Skip to content

Commit 473b262

Browse files
committed
Auto merge of #17227 - Veykril:build-deps-changed-hashes, r=Veykril
fix: Hash file contents to verify whether file actually changed Fixes rust-lang/rust-analyzer#16580
2 parents d580944 + 32827d2 commit 473b262

File tree

5 files changed

+94
-97
lines changed

5 files changed

+94
-97
lines changed

src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
//!
3030
//! In general, any item in the `ItemTree` stores its `AstId`, which allows mapping it back to its
3131
//! surface syntax.
32+
#![allow(unexpected_cfgs)]
3233

3334
mod lower;
3435
mod pretty;
@@ -467,13 +468,12 @@ macro_rules! mod_items {
467468
pub enum GenericModItem {
468469
$(
469470
$(
470-
#[cfg_attr(FALSE, $generic_params)]
471+
#[cfg_attr(ignore_fragment, $generic_params)]
471472
$typ(FileItemTreeId<$typ>),
472473
)?
473474
)+
474475
}
475476

476-
#[allow(unexpected_cfgs)]
477477
impl From<GenericModItem> for ModItem {
478478
fn from(id: GenericModItem) -> ModItem {
479479
match id {
@@ -494,7 +494,6 @@ macro_rules! mod_items {
494494
}
495495

496496
$(
497-
#[allow(unexpected_cfgs)]
498497
impl From<FileItemTreeId<$typ>> for ModItem {
499498
fn from(id: FileItemTreeId<$typ>) -> ModItem {
500499
ModItem::$typ(id)

src/tools/rust-analyzer/crates/load-cargo/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -361,8 +361,8 @@ fn load_crate_graph(
361361
}
362362
}
363363
let changes = vfs.take_changes();
364-
for file in changes {
365-
if let vfs::Change::Create(v) | vfs::Change::Modify(v) = file.change {
364+
for (_, file) in changes {
365+
if let vfs::Change::Create(v, _) | vfs::Change::Modify(v, _) = file.change {
366366
if let Ok(text) = String::from_utf8(v) {
367367
analysis_change.change_file(file.file_id, Some(text))
368368
}

src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs

Lines changed: 14 additions & 45 deletions
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::{collections::hash_map::Entry, time::Instant};
6+
use std::time::Instant;
77

88
use crossbeam_channel::{unbounded, Receiver, Sender};
99
use flycheck::FlycheckHandle;
@@ -25,7 +25,7 @@ use project_model::{
2525
use rustc_hash::{FxHashMap, FxHashSet};
2626
use tracing::{span, Level};
2727
use triomphe::Arc;
28-
use vfs::{AnchoredPathBuf, ChangedFile, Vfs};
28+
use vfs::{AnchoredPathBuf, Vfs};
2929

3030
use crate::{
3131
config::{Config, ConfigError},
@@ -254,7 +254,6 @@ impl GlobalState {
254254

255255
pub(crate) fn process_changes(&mut self) -> bool {
256256
let _p = span!(Level::INFO, "GlobalState::process_changes").entered();
257-
let mut file_changes = FxHashMap::<_, ChangedFile>::default();
258257
let (change, modified_rust_files, workspace_structure_change) = {
259258
let mut change = ChangeWithProcMacros::new();
260259
let mut guard = self.vfs.write();
@@ -266,44 +265,13 @@ impl GlobalState {
266265
// downgrade to read lock to allow more readers while we are normalizing text
267266
let guard = RwLockWriteGuard::downgrade_to_upgradable(guard);
268267
let vfs: &Vfs = &guard.0;
269-
// We need to fix up the changed events a bit. If we have a create or modify for a file
270-
// id that is followed by a delete we actually skip observing the file text from the
271-
// earlier event, to avoid problems later on.
272-
for changed_file in changed_files {
273-
use vfs::Change::*;
274-
match file_changes.entry(changed_file.file_id) {
275-
Entry::Occupied(mut o) => {
276-
let change = o.get_mut();
277-
match (&mut change.change, changed_file.change) {
278-
// latter `Delete` wins
279-
(change, Delete) => *change = Delete,
280-
// merge `Create` with `Create` or `Modify`
281-
(Create(prev), Create(new) | Modify(new)) => *prev = new,
282-
// collapse identical `Modify`es
283-
(Modify(prev), Modify(new)) => *prev = new,
284-
// equivalent to `Modify`
285-
(change @ Delete, Create(new)) => {
286-
*change = Modify(new);
287-
}
288-
// shouldn't occur, but collapse into `Create`
289-
(change @ Delete, Modify(new)) => {
290-
stdx::never!();
291-
*change = Create(new);
292-
}
293-
// shouldn't occur, but keep the Create
294-
(prev @ Modify(_), new @ Create(_)) => *prev = new,
295-
}
296-
}
297-
Entry::Vacant(v) => _ = v.insert(changed_file),
298-
}
299-
}
300268

301269
let mut workspace_structure_change = None;
302270
// A file was added or deleted
303271
let mut has_structure_changes = false;
304272
let mut bytes = vec![];
305273
let mut modified_rust_files = vec![];
306-
for file in file_changes.into_values() {
274+
for file in changed_files.into_values() {
307275
let vfs_path = vfs.file_path(file.file_id);
308276
if let Some(path) = vfs_path.as_path() {
309277
has_structure_changes |= file.is_created_or_deleted();
@@ -326,16 +294,17 @@ impl GlobalState {
326294
self.diagnostics.clear_native_for(file.file_id);
327295
}
328296

329-
let text = if let vfs::Change::Create(v) | vfs::Change::Modify(v) = file.change {
330-
String::from_utf8(v).ok().map(|text| {
331-
// FIXME: Consider doing normalization in the `vfs` instead? That allows
332-
// getting rid of some locking
333-
let (text, line_endings) = LineEndings::normalize(text);
334-
(text, line_endings)
335-
})
336-
} else {
337-
None
338-
};
297+
let text =
298+
if let vfs::Change::Create(v, _) | vfs::Change::Modify(v, _) = file.change {
299+
String::from_utf8(v).ok().map(|text| {
300+
// FIXME: Consider doing normalization in the `vfs` instead? That allows
301+
// getting rid of some locking
302+
let (text, line_endings) = LineEndings::normalize(text);
303+
(text, line_endings)
304+
})
305+
} else {
306+
None
307+
};
339308
// delay `line_endings_map` changes until we are done normalizing the text
340309
// this allows delaying the re-acquisition of the write lock
341310
bytes.push((file.file_id, text));

src/tools/rust-analyzer/crates/stdx/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ pub fn is_ci() -> bool {
2222
option_env!("CI").is_some()
2323
}
2424

25+
pub fn hash_once<Hasher: std::hash::Hasher + Default>(thing: impl std::hash::Hash) -> u64 {
26+
std::hash::BuildHasher::hash_one(&std::hash::BuildHasherDefault::<Hasher>::default(), thing)
27+
}
28+
2529
#[must_use]
2630
#[allow(clippy::print_stderr)]
2731
pub fn timeit(label: &'static str) -> impl Drop {

src/tools/rust-analyzer/crates/vfs/src/lib.rs

Lines changed: 72 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,19 @@ pub mod loader;
4646
mod path_interner;
4747
mod vfs_path;
4848

49-
use std::{fmt, mem};
49+
use std::{fmt, hash::BuildHasherDefault, mem};
5050

5151
use crate::path_interner::PathInterner;
5252

5353
pub use crate::{
5454
anchored_path::{AnchoredPath, AnchoredPathBuf},
5555
vfs_path::VfsPath,
5656
};
57+
use indexmap::{map::Entry, IndexMap};
5758
pub use paths::{AbsPath, AbsPathBuf};
5859

60+
use rustc_hash::FxHasher;
61+
use stdx::hash_once;
5962
use tracing::{span, Level};
6063

6164
/// Handle to a file in [`Vfs`]
@@ -93,20 +96,13 @@ impl nohash_hasher::IsEnabled for FileId {}
9396
pub struct Vfs {
9497
interner: PathInterner,
9598
data: Vec<FileState>,
96-
// FIXME: This should be a HashMap<FileId, ChangeFile>
97-
// right now we do a nasty deduplication in GlobalState::process_changes that would be a lot
98-
// easier to handle here on insertion.
99-
changes: Vec<ChangedFile>,
100-
// The above FIXME would then also get rid of this probably
101-
created_this_cycle: Vec<FileId>,
99+
changes: IndexMap<FileId, ChangedFile, BuildHasherDefault<FxHasher>>,
102100
}
103101

104102
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
105103
pub enum FileState {
106-
/// The file has been created this cycle.
107-
Created,
108-
/// The file exists.
109-
Exists,
104+
/// The file exists with the given content hash.
105+
Exists(u64),
110106
/// The file is deleted.
111107
Deleted,
112108
}
@@ -129,23 +125,23 @@ impl ChangedFile {
129125
/// Returns `true` if the change is [`Create`](ChangeKind::Create) or
130126
/// [`Delete`](Change::Delete).
131127
pub fn is_created_or_deleted(&self) -> bool {
132-
matches!(self.change, Change::Create(_) | Change::Delete)
128+
matches!(self.change, Change::Create(_, _) | Change::Delete)
133129
}
134130

135131
/// Returns `true` if the change is [`Create`](ChangeKind::Create).
136132
pub fn is_created(&self) -> bool {
137-
matches!(self.change, Change::Create(_))
133+
matches!(self.change, Change::Create(_, _))
138134
}
139135

140136
/// Returns `true` if the change is [`Modify`](ChangeKind::Modify).
141137
pub fn is_modified(&self) -> bool {
142-
matches!(self.change, Change::Modify(_))
138+
matches!(self.change, Change::Modify(_, _))
143139
}
144140

145141
pub fn kind(&self) -> ChangeKind {
146142
match self.change {
147-
Change::Create(_) => ChangeKind::Create,
148-
Change::Modify(_) => ChangeKind::Modify,
143+
Change::Create(_, _) => ChangeKind::Create,
144+
Change::Modify(_, _) => ChangeKind::Modify,
149145
Change::Delete => ChangeKind::Delete,
150146
}
151147
}
@@ -155,9 +151,9 @@ impl ChangedFile {
155151
#[derive(Eq, PartialEq, Debug)]
156152
pub enum Change {
157153
/// The file was (re-)created
158-
Create(Vec<u8>),
154+
Create(Vec<u8>, u64),
159155
/// The file was modified
160-
Modify(Vec<u8>),
156+
Modify(Vec<u8>, u64),
161157
/// The file was deleted
162158
Delete,
163159
}
@@ -176,9 +172,7 @@ pub enum ChangeKind {
176172
impl Vfs {
177173
/// Id of the given path if it exists in the `Vfs` and is not deleted.
178174
pub fn file_id(&self, path: &VfsPath) -> Option<FileId> {
179-
self.interner
180-
.get(path)
181-
.filter(|&it| matches!(self.get(it), FileState::Exists | FileState::Created))
175+
self.interner.get(path).filter(|&it| matches!(self.get(it), FileState::Exists(_)))
182176
}
183177

184178
/// File path corresponding to the given `file_id`.
@@ -196,9 +190,7 @@ impl Vfs {
196190
pub fn iter(&self) -> impl Iterator<Item = (FileId, &VfsPath)> + '_ {
197191
(0..self.data.len())
198192
.map(|it| FileId(it as u32))
199-
.filter(move |&file_id| {
200-
matches!(self.get(file_id), FileState::Exists | FileState::Created)
201-
})
193+
.filter(move |&file_id| matches!(self.get(file_id), FileState::Exists(_)))
202194
.map(move |file_id| {
203195
let path = self.interner.lookup(file_id);
204196
(file_id, path)
@@ -217,41 +209,74 @@ impl Vfs {
217209
let state = self.get(file_id);
218210
let change_kind = match (state, contents) {
219211
(FileState::Deleted, None) => return false,
220-
(FileState::Deleted, Some(v)) => Change::Create(v),
221-
(FileState::Exists | FileState::Created, None) => Change::Delete,
222-
(FileState::Exists | FileState::Created, Some(v)) => Change::Modify(v),
223-
};
224-
self.data[file_id.0 as usize] = match change_kind {
225-
Change::Create(_) => {
226-
self.created_this_cycle.push(file_id);
227-
FileState::Created
212+
(FileState::Deleted, Some(v)) => {
213+
let hash = hash_once::<FxHasher>(&*v);
214+
Change::Create(v, hash)
215+
}
216+
(FileState::Exists(_), None) => Change::Delete,
217+
(FileState::Exists(hash), Some(v)) => {
218+
let new_hash = hash_once::<FxHasher>(&*v);
219+
if new_hash == hash {
220+
return false;
221+
}
222+
Change::Modify(v, new_hash)
228223
}
229-
// If the file got created this cycle, make sure we keep it that way even
230-
// if a modify comes in
231-
Change::Modify(_) if matches!(state, FileState::Created) => FileState::Created,
232-
Change::Modify(_) => FileState::Exists,
233-
Change::Delete => FileState::Deleted,
234224
};
225+
226+
let mut set_data = |change_kind| {
227+
self.data[file_id.0 as usize] = match change_kind {
228+
&Change::Create(_, hash) | &Change::Modify(_, hash) => FileState::Exists(hash),
229+
Change::Delete => FileState::Deleted,
230+
};
231+
};
232+
235233
let changed_file = ChangedFile { file_id, change: change_kind };
236-
self.changes.push(changed_file);
234+
match self.changes.entry(file_id) {
235+
// two changes to the same file in one cycle, merge them appropriately
236+
Entry::Occupied(mut o) => {
237+
use Change::*;
238+
239+
match (&mut o.get_mut().change, changed_file.change) {
240+
// newer `Delete` wins
241+
(change, Delete) => *change = Delete,
242+
// merge `Create` with `Create` or `Modify`
243+
(Create(prev, old_hash), Create(new, new_hash) | Modify(new, new_hash)) => {
244+
*prev = new;
245+
*old_hash = new_hash;
246+
}
247+
// collapse identical `Modify`es
248+
(Modify(prev, old_hash), Modify(new, new_hash)) => {
249+
*prev = new;
250+
*old_hash = new_hash;
251+
}
252+
// equivalent to `Modify`
253+
(change @ Delete, Create(new, new_hash)) => {
254+
*change = Modify(new, new_hash);
255+
}
256+
// shouldn't occur, but collapse into `Create`
257+
(change @ Delete, Modify(new, new_hash)) => {
258+
stdx::never!();
259+
*change = Create(new, new_hash);
260+
}
261+
// shouldn't occur, but keep the Create
262+
(prev @ Modify(_, _), new @ Create(_, _)) => *prev = new,
263+
}
264+
set_data(&o.get().change);
265+
}
266+
Entry::Vacant(v) => set_data(&v.insert(changed_file).change),
267+
};
268+
237269
true
238270
}
239271

240272
/// Drain and returns all the changes in the `Vfs`.
241-
pub fn take_changes(&mut self) -> Vec<ChangedFile> {
242-
let _p = span!(Level::INFO, "Vfs::take_changes").entered();
243-
for file_id in self.created_this_cycle.drain(..) {
244-
if self.data[file_id.0 as usize] == FileState::Created {
245-
// downgrade the file from `Created` to `Exists` as the cycle is done
246-
self.data[file_id.0 as usize] = FileState::Exists;
247-
}
248-
}
273+
pub fn take_changes(&mut self) -> IndexMap<FileId, ChangedFile, BuildHasherDefault<FxHasher>> {
249274
mem::take(&mut self.changes)
250275
}
251276

252277
/// Provides a panic-less way to verify file_id validity.
253278
pub fn exists(&self, file_id: FileId) -> bool {
254-
matches!(self.get(file_id), FileState::Exists | FileState::Created)
279+
matches!(self.get(file_id), FileState::Exists(_))
255280
}
256281

257282
/// Returns the id associated with `path`

0 commit comments

Comments
 (0)