@@ -46,16 +46,19 @@ pub mod loader;
46
46
mod path_interner;
47
47
mod vfs_path;
48
48
49
- use std:: { fmt, mem} ;
49
+ use std:: { fmt, hash :: BuildHasherDefault , mem} ;
50
50
51
51
use crate :: path_interner:: PathInterner ;
52
52
53
53
pub use crate :: {
54
54
anchored_path:: { AnchoredPath , AnchoredPathBuf } ,
55
55
vfs_path:: VfsPath ,
56
56
} ;
57
+ use indexmap:: { map:: Entry , IndexMap } ;
57
58
pub use paths:: { AbsPath , AbsPathBuf } ;
58
59
60
+ use rustc_hash:: FxHasher ;
61
+ use stdx:: hash_once;
59
62
use tracing:: { span, Level } ;
60
63
61
64
/// Handle to a file in [`Vfs`]
@@ -93,20 +96,13 @@ impl nohash_hasher::IsEnabled for FileId {}
93
96
pub struct Vfs {
94
97
interner : PathInterner ,
95
98
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 > > ,
102
100
}
103
101
104
102
#[ derive( Copy , Clone , Debug , PartialEq , PartialOrd ) ]
105
103
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 ) ,
110
106
/// The file is deleted.
111
107
Deleted ,
112
108
}
@@ -129,23 +125,23 @@ impl ChangedFile {
129
125
/// Returns `true` if the change is [`Create`](ChangeKind::Create) or
130
126
/// [`Delete`](Change::Delete).
131
127
pub fn is_created_or_deleted ( & self ) -> bool {
132
- matches ! ( self . change, Change :: Create ( _) | Change :: Delete )
128
+ matches ! ( self . change, Change :: Create ( _, _ ) | Change :: Delete )
133
129
}
134
130
135
131
/// Returns `true` if the change is [`Create`](ChangeKind::Create).
136
132
pub fn is_created ( & self ) -> bool {
137
- matches ! ( self . change, Change :: Create ( _) )
133
+ matches ! ( self . change, Change :: Create ( _, _ ) )
138
134
}
139
135
140
136
/// Returns `true` if the change is [`Modify`](ChangeKind::Modify).
141
137
pub fn is_modified ( & self ) -> bool {
142
- matches ! ( self . change, Change :: Modify ( _) )
138
+ matches ! ( self . change, Change :: Modify ( _, _ ) )
143
139
}
144
140
145
141
pub fn kind ( & self ) -> ChangeKind {
146
142
match self . change {
147
- Change :: Create ( _) => ChangeKind :: Create ,
148
- Change :: Modify ( _) => ChangeKind :: Modify ,
143
+ Change :: Create ( _, _ ) => ChangeKind :: Create ,
144
+ Change :: Modify ( _, _ ) => ChangeKind :: Modify ,
149
145
Change :: Delete => ChangeKind :: Delete ,
150
146
}
151
147
}
@@ -155,9 +151,9 @@ impl ChangedFile {
155
151
#[ derive( Eq , PartialEq , Debug ) ]
156
152
pub enum Change {
157
153
/// The file was (re-)created
158
- Create ( Vec < u8 > ) ,
154
+ Create ( Vec < u8 > , u64 ) ,
159
155
/// The file was modified
160
- Modify ( Vec < u8 > ) ,
156
+ Modify ( Vec < u8 > , u64 ) ,
161
157
/// The file was deleted
162
158
Delete ,
163
159
}
@@ -176,9 +172,7 @@ pub enum ChangeKind {
176
172
impl Vfs {
177
173
/// Id of the given path if it exists in the `Vfs` and is not deleted.
178
174
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 ( _) ) )
182
176
}
183
177
184
178
/// File path corresponding to the given `file_id`.
@@ -196,9 +190,7 @@ impl Vfs {
196
190
pub fn iter ( & self ) -> impl Iterator < Item = ( FileId , & VfsPath ) > + ' _ {
197
191
( 0 ..self . data . len ( ) )
198
192
. 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 ( _) ) )
202
194
. map ( move |file_id| {
203
195
let path = self . interner . lookup ( file_id) ;
204
196
( file_id, path)
@@ -217,41 +209,74 @@ impl Vfs {
217
209
let state = self . get ( file_id) ;
218
210
let change_kind = match ( state, contents) {
219
211
( 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)
228
223
}
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 ,
234
224
} ;
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
+
235
233
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
+
237
269
true
238
270
}
239
271
240
272
/// 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 > > {
249
274
mem:: take ( & mut self . changes )
250
275
}
251
276
252
277
/// Provides a panic-less way to verify file_id validity.
253
278
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 ( _ ) )
255
280
}
256
281
257
282
/// Returns the id associated with `path`
0 commit comments