Skip to content

Commit c1c2421

Browse files
committed
Auto merge of rust-lang#16307 - Veykril:vfs2, r=lnicola
internal: VFS no longer stores all source files in memory Turns out there is no need to keep the files around. We either upload them to salsa once processed, or we need to keep them around for the `DidChangeTextDocumentNotification`, but that notification is only valid for opened documents, so instead we can just keep the files around in the `MemDocs`! Fixes rust-lang/rust-analyzer#16301
2 parents af40101 + 1c40ac7 commit c1c2421

File tree

9 files changed

+130
-143
lines changed

9 files changed

+130
-143
lines changed

crates/load-cargo/src/lib.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -331,9 +331,8 @@ fn load_crate_graph(
331331
}
332332
let changes = vfs.take_changes();
333333
for file in changes {
334-
if file.exists() {
335-
let contents = vfs.file_contents(file.file_id);
336-
if let Ok(text) = std::str::from_utf8(contents) {
334+
if let vfs::Change::Create(v) | vfs::Change::Modify(v) = file.change {
335+
if let Ok(text) = std::str::from_utf8(&v) {
337336
analysis_change.change_file(file.file_id, Some(text.into()))
338337
}
339338
}

crates/rust-analyzer/src/global_state.rs

+37-46
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//!
44
//! Each tick provides an immutable snapshot of the state as `WorldSnapshot`.
55
6-
use std::time::Instant;
6+
use std::{collections::hash_map::Entry, time::Instant};
77

88
use crossbeam_channel::{unbounded, Receiver, Sender};
99
use flycheck::FlycheckHandle;
@@ -21,7 +21,7 @@ use proc_macro_api::ProcMacroServer;
2121
use project_model::{CargoWorkspace, ProjectWorkspace, Target, WorkspaceBuildScripts};
2222
use rustc_hash::{FxHashMap, FxHashSet};
2323
use triomphe::Arc;
24-
use vfs::{AnchoredPathBuf, Vfs};
24+
use vfs::{AnchoredPathBuf, ChangedFile, Vfs};
2525

2626
use crate::{
2727
config::{Config, ConfigError},
@@ -217,8 +217,8 @@ impl GlobalState {
217217
pub(crate) fn process_changes(&mut self) -> bool {
218218
let _p = profile::span("GlobalState::process_changes");
219219

220-
let mut file_changes = FxHashMap::default();
221-
let (change, changed_files, workspace_structure_change) = {
220+
let mut file_changes = FxHashMap::<_, (bool, ChangedFile)>::default();
221+
let (change, modified_files, workspace_structure_change) = {
222222
let mut change = Change::new();
223223
let mut guard = self.vfs.write();
224224
let changed_files = guard.0.take_changes();
@@ -233,64 +233,63 @@ impl GlobalState {
233233
// id that is followed by a delete we actually skip observing the file text from the
234234
// earlier event, to avoid problems later on.
235235
for changed_file in changed_files {
236-
use vfs::ChangeKind::*;
237-
238-
file_changes
239-
.entry(changed_file.file_id)
240-
.and_modify(|(change, just_created)| {
241-
// None -> Delete => keep
242-
// Create -> Delete => collapse
243-
//
244-
match (change, just_created, changed_file.change_kind) {
236+
use vfs::Change::*;
237+
match file_changes.entry(changed_file.file_id) {
238+
Entry::Occupied(mut o) => {
239+
let (just_created, change) = o.get_mut();
240+
match (&mut change.change, just_created, changed_file.change) {
245241
// latter `Delete` wins
246242
(change, _, Delete) => *change = Delete,
247243
// merge `Create` with `Create` or `Modify`
248-
(Create, _, Create | Modify) => {}
244+
(Create(prev), _, Create(new) | Modify(new)) => *prev = new,
249245
// collapse identical `Modify`es
250-
(Modify, _, Modify) => {}
246+
(Modify(prev), _, Modify(new)) => *prev = new,
251247
// equivalent to `Modify`
252-
(change @ Delete, just_created, Create) => {
253-
*change = Modify;
248+
(change @ Delete, just_created, Create(new)) => {
249+
*change = Modify(new);
254250
*just_created = true;
255251
}
256252
// shouldn't occur, but collapse into `Create`
257-
(change @ Delete, just_created, Modify) => {
258-
*change = Create;
253+
(change @ Delete, just_created, Modify(new)) => {
254+
*change = Create(new);
259255
*just_created = true;
260256
}
261257
// shouldn't occur, but collapse into `Modify`
262-
(Modify, _, Create) => {}
258+
(Modify(prev), _, Create(new)) => *prev = new,
263259
}
264-
})
265-
.or_insert((
266-
changed_file.change_kind,
267-
matches!(changed_file.change_kind, Create),
268-
));
260+
}
261+
Entry::Vacant(v) => {
262+
_ = v.insert((matches!(&changed_file.change, Create(_)), changed_file))
263+
}
264+
}
269265
}
270266

271267
let changed_files: Vec<_> = file_changes
272268
.into_iter()
273-
.filter(|(_, (change_kind, just_created))| {
274-
!matches!((change_kind, just_created), (vfs::ChangeKind::Delete, true))
269+
.filter(|(_, (just_created, change))| {
270+
!(*just_created && matches!(change.change, vfs::Change::Delete))
275271
})
276-
.map(|(file_id, (change_kind, _))| vfs::ChangedFile { file_id, change_kind })
272+
.map(|(file_id, (_, change))| vfs::ChangedFile { file_id, ..change })
277273
.collect();
278274

279275
let mut workspace_structure_change = None;
280276
// A file was added or deleted
281277
let mut has_structure_changes = false;
282278
let mut bytes = vec![];
283-
for file in &changed_files {
279+
let mut modified_files = vec![];
280+
for file in changed_files {
284281
let vfs_path = &vfs.file_path(file.file_id);
285282
if let Some(path) = vfs_path.as_path() {
286283
let path = path.to_path_buf();
287-
if reload::should_refresh_for_change(&path, file.change_kind) {
284+
if reload::should_refresh_for_change(&path, file.kind()) {
288285
workspace_structure_change = Some((path.clone(), false));
289286
}
290287
if file.is_created_or_deleted() {
291288
has_structure_changes = true;
292289
workspace_structure_change =
293290
Some((path, self.crate_graph_file_dependencies.contains(vfs_path)));
291+
} else {
292+
modified_files.push(file.file_id);
294293
}
295294
}
296295

@@ -299,10 +298,8 @@ impl GlobalState {
299298
self.diagnostics.clear_native_for(file.file_id);
300299
}
301300

302-
let text = if file.exists() {
303-
let bytes = vfs.file_contents(file.file_id).to_vec();
304-
305-
String::from_utf8(bytes).ok().and_then(|text| {
301+
let text = if let vfs::Change::Create(v) | vfs::Change::Modify(v) = file.change {
302+
String::from_utf8(v).ok().and_then(|text| {
306303
// FIXME: Consider doing normalization in the `vfs` instead? That allows
307304
// getting rid of some locking
308305
let (text, line_endings) = LineEndings::normalize(text);
@@ -327,11 +324,10 @@ impl GlobalState {
327324
let roots = self.source_root_config.partition(vfs);
328325
change.set_roots(roots);
329326
}
330-
(change, changed_files, workspace_structure_change)
327+
(change, modified_files, workspace_structure_change)
331328
};
332329

333330
self.analysis_host.apply_change(change);
334-
335331
{
336332
let raw_database = self.analysis_host.raw_database();
337333
// FIXME: ideally we should only trigger a workspace fetch for non-library changes
@@ -343,13 +339,12 @@ impl GlobalState {
343339
force_crate_graph_reload,
344340
);
345341
}
346-
self.proc_macro_changed =
347-
changed_files.iter().filter(|file| !file.is_created_or_deleted()).any(|file| {
348-
let crates = raw_database.relevant_crates(file.file_id);
349-
let crate_graph = raw_database.crate_graph();
342+
self.proc_macro_changed = modified_files.into_iter().any(|file_id| {
343+
let crates = raw_database.relevant_crates(file_id);
344+
let crate_graph = raw_database.crate_graph();
350345

351-
crates.iter().any(|&krate| crate_graph[krate].is_proc_macro)
352-
});
346+
crates.iter().any(|&krate| crate_graph[krate].is_proc_macro)
347+
});
353348
}
354349

355350
true
@@ -494,10 +489,6 @@ impl GlobalStateSnapshot {
494489
})
495490
}
496491

497-
pub(crate) fn vfs_memory_usage(&self) -> usize {
498-
self.vfs_read().memory_usage()
499-
}
500-
501492
pub(crate) fn file_exists(&self, file_id: FileId) -> bool {
502493
self.vfs.read().0.exists(file_id)
503494
}

crates/rust-analyzer/src/handlers/notification.rs

+15-10
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,13 @@ pub(crate) fn handle_did_open_text_document(
5959
if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
6060
let already_exists = state
6161
.mem_docs
62-
.insert(path.clone(), DocumentData::new(params.text_document.version))
62+
.insert(
63+
path.clone(),
64+
DocumentData::new(
65+
params.text_document.version,
66+
params.text_document.text.clone().into_bytes(),
67+
),
68+
)
6369
.is_err();
6470
if already_exists {
6571
tracing::error!("duplicate DidOpenTextDocument: {}", path);
@@ -76,28 +82,27 @@ pub(crate) fn handle_did_change_text_document(
7682
let _p = profile::span("handle_did_change_text_document");
7783

7884
if let Ok(path) = from_proto::vfs_path(&params.text_document.uri) {
79-
match state.mem_docs.get_mut(&path) {
85+
let data = match state.mem_docs.get_mut(&path) {
8086
Some(doc) => {
8187
// The version passed in DidChangeTextDocument is the version after all edits are applied
8288
// so we should apply it before the vfs is notified.
8389
doc.version = params.text_document.version;
90+
&mut doc.data
8491
}
8592
None => {
8693
tracing::error!("unexpected DidChangeTextDocument: {}", path);
8794
return Ok(());
8895
}
8996
};
9097

91-
let text = apply_document_changes(
98+
let new_contents = apply_document_changes(
9299
state.config.position_encoding(),
93-
|| {
94-
let vfs = &state.vfs.read().0;
95-
let file_id = vfs.file_id(&path).unwrap();
96-
std::str::from_utf8(vfs.file_contents(file_id)).unwrap().into()
97-
},
100+
std::str::from_utf8(data).unwrap(),
98101
params.content_changes,
99-
);
100-
state.vfs.write().0.set_file_contents(path, Some(text.into_bytes()));
102+
)
103+
.into_bytes();
104+
*data = new_contents.clone();
105+
state.vfs.write().0.set_file_contents(path, Some(new_contents));
101106
}
102107
Ok(())
103108
}

crates/rust-analyzer/src/handlers/request.rs

-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ pub(crate) fn handle_analyzer_status(
103103
.collect::<Vec<&AbsPath>>()
104104
);
105105
}
106-
format_to!(buf, "\nVfs memory usage: {}\n", profile::Bytes::new(snap.vfs_memory_usage() as _));
107106
buf.push_str("\nAnalysis:\n");
108107
buf.push_str(
109108
&snap

crates/rust-analyzer/src/lsp/utils.rs

+16-19
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ impl GlobalState {
168168

169169
pub(crate) fn apply_document_changes(
170170
encoding: PositionEncoding,
171-
file_contents: impl FnOnce() -> String,
171+
file_contents: &str,
172172
mut content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>,
173173
) -> String {
174174
// If at least one of the changes is a full document change, use the last
@@ -179,7 +179,7 @@ pub(crate) fn apply_document_changes(
179179
let text = mem::take(&mut content_changes[idx].text);
180180
(text, &content_changes[idx + 1..])
181181
}
182-
None => (file_contents(), &content_changes[..]),
182+
None => (file_contents.to_owned(), &content_changes[..]),
183183
};
184184
if content_changes.is_empty() {
185185
return text;
@@ -276,61 +276,58 @@ mod tests {
276276
}
277277

278278
let encoding = PositionEncoding::Wide(WideEncoding::Utf16);
279-
let text = apply_document_changes(encoding, || String::new(), vec![]);
279+
let text = apply_document_changes(encoding, "", vec![]);
280280
assert_eq!(text, "");
281281
let text = apply_document_changes(
282282
encoding,
283-
|| text,
283+
&text,
284284
vec![TextDocumentContentChangeEvent {
285285
range: None,
286286
range_length: None,
287287
text: String::from("the"),
288288
}],
289289
);
290290
assert_eq!(text, "the");
291-
let text = apply_document_changes(encoding, || text, c![0, 3; 0, 3 => " quick"]);
291+
let text = apply_document_changes(encoding, &text, c![0, 3; 0, 3 => " quick"]);
292292
assert_eq!(text, "the quick");
293293
let text =
294-
apply_document_changes(encoding, || text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]);
294+
apply_document_changes(encoding, &text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]);
295295
assert_eq!(text, "quick foxes");
296-
let text = apply_document_changes(encoding, || text, c![0, 11; 0, 11 => "\ndream"]);
296+
let text = apply_document_changes(encoding, &text, c![0, 11; 0, 11 => "\ndream"]);
297297
assert_eq!(text, "quick foxes\ndream");
298-
let text = apply_document_changes(encoding, || text, c![1, 0; 1, 0 => "have "]);
298+
let text = apply_document_changes(encoding, &text, c![1, 0; 1, 0 => "have "]);
299299
assert_eq!(text, "quick foxes\nhave dream");
300300
let text = apply_document_changes(
301301
encoding,
302-
|| text,
302+
&text,
303303
c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"],
304304
);
305305
assert_eq!(text, "the quick foxes\nhave quiet dreams\n");
306-
let text = apply_document_changes(
307-
encoding,
308-
|| text,
309-
c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"],
310-
);
306+
let text =
307+
apply_document_changes(encoding, &text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]);
311308
assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n");
312309
let text = apply_document_changes(
313310
encoding,
314-
|| text,
311+
&text,
315312
c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"],
316313
);
317314
assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n");
318315
let text =
319-
apply_document_changes(encoding, || text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]);
316+
apply_document_changes(encoding, &text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]);
320317
assert_eq!(text, "the quick \nthey have quiet dreams\n");
321318

322319
let text = String::from("❤️");
323-
let text = apply_document_changes(encoding, || text, c![0, 0; 0, 0 => "a"]);
320+
let text = apply_document_changes(encoding, &text, c![0, 0; 0, 0 => "a"]);
324321
assert_eq!(text, "a❤️");
325322

326323
let text = String::from("a\nb");
327324
let text =
328-
apply_document_changes(encoding, || text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]);
325+
apply_document_changes(encoding, &text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]);
329326
assert_eq!(text, "adcb");
330327

331328
let text = String::from("a\nb");
332329
let text =
333-
apply_document_changes(encoding, || text, c![0, 1; 1, 0 => \nc", 0, 2; 0, 2 => "c"]);
330+
apply_document_changes(encoding, &text, c![0, 1; 1, 0 => \nc", 0, 2; 0, 2 => "c"]);
334331
assert_eq!(text, "ațc\ncb");
335332
}
336333

crates/rust-analyzer/src/main_loop.rs

+2
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,8 @@ impl GlobalState {
576576
let vfs = &mut self.vfs.write().0;
577577
for (path, contents) in files {
578578
let path = VfsPath::from(path);
579+
// if the file is in mem docs, it's managed by the client via notifications
580+
// so only set it if its not in there
579581
if !self.mem_docs.contains(&path) {
580582
vfs.set_file_contents(path, contents);
581583
}

crates/rust-analyzer/src/mem_docs.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,11 @@ impl MemDocs {
6262
#[derive(Debug, Clone)]
6363
pub(crate) struct DocumentData {
6464
pub(crate) version: i32,
65+
pub(crate) data: Vec<u8>,
6566
}
6667

6768
impl DocumentData {
68-
pub(crate) fn new(version: i32) -> Self {
69-
DocumentData { version }
69+
pub(crate) fn new(version: i32, data: Vec<u8>) -> Self {
70+
DocumentData { version, data }
7071
}
7172
}

crates/rust-analyzer/src/reload.rs

+3-4
Original file line numberDiff line numberDiff line change
@@ -503,10 +503,9 @@ impl GlobalState {
503503
match vfs.file_id(&vfs_path) {
504504
Some(file_id) => Some(file_id),
505505
None => {
506-
if !self.mem_docs.contains(&vfs_path) {
507-
let contents = loader.handle.load_sync(path);
508-
vfs.set_file_contents(vfs_path.clone(), contents);
509-
}
506+
// FIXME: Consider not loading this here?
507+
let contents = loader.handle.load_sync(path);
508+
vfs.set_file_contents(vfs_path.clone(), contents);
510509
vfs.file_id(&vfs_path)
511510
}
512511
}

0 commit comments

Comments
 (0)