@@ -46,16 +46,19 @@ pub mod loader;
4646mod path_interner;
4747mod vfs_path;
4848
49- use std:: { fmt, mem} ;
49+ use std:: { fmt, hash :: BuildHasherDefault , mem} ;
5050
5151use crate :: path_interner:: PathInterner ;
5252
5353pub use crate :: {
5454 anchored_path:: { AnchoredPath , AnchoredPathBuf } ,
5555 vfs_path:: VfsPath ,
5656} ;
57+ use indexmap:: { map:: Entry , IndexMap } ;
5758pub use paths:: { AbsPath , AbsPathBuf } ;
5859
60+ use rustc_hash:: FxHasher ;
61+ use stdx:: hash_once;
5962use tracing:: { span, Level } ;
6063
6164/// Handle to a file in [`Vfs`]
@@ -93,20 +96,13 @@ impl nohash_hasher::IsEnabled for FileId {}
9396pub 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 ) ]
105103pub 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 ) ]
156152pub 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 {
176172impl 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