@@ -252,6 +252,7 @@ impl Project {
252252 . map ( IOErrorDiagnostic :: to_diagnostic) ,
253253 ) ;
254254
255+ let open_files = self . open_files ( db) ;
255256 let check_start = ruff_db:: Instant :: now ( ) ;
256257 let file_diagnostics = std:: sync:: Mutex :: new ( vec ! [ ] ) ;
257258
@@ -269,11 +270,30 @@ impl Project {
269270 tracing:: debug_span!( parent: project_span, "check_file" , ?file) ;
270271 let _entered = check_file_span. entered ( ) ;
271272
272- let result = check_file_impl ( & db, file) ;
273- file_diagnostics
274- . lock ( )
275- . unwrap ( )
276- . extend ( result. iter ( ) . map ( Clone :: clone) ) ;
273+ match check_file_impl ( & db, file) {
274+ Ok ( diagnostics) => {
275+ file_diagnostics
276+ . lock ( )
277+ . unwrap ( )
278+ . extend ( diagnostics. iter ( ) . map ( Clone :: clone) ) ;
279+
280+ // This is outside `check_file_impl` to avoid that opening or closing
281+ // a file invalidates the `check_file_impl` query of every file!
282+ if !open_files. contains ( & file) {
283+ // The module has already been parsed by `check_file_impl`.
284+ // We only retrieve it here so that we can call `clear` on it.
285+ let parsed = parsed_module ( & db, file) ;
286+
287+ // Drop the AST now that we are done checking this file. It is not currently open,
288+ // so it is unlikely to be accessed again soon. If any queries need to access the AST
289+ // from across files, it will be re-parsed.
290+ parsed. clear ( ) ;
291+ }
292+ }
293+ Err ( io_error) => {
294+ file_diagnostics. lock ( ) . unwrap ( ) . push ( io_error. clone ( ) ) ;
295+ }
296+ }
277297
278298 reporter. report_file ( & file) ;
279299 } ) ;
@@ -300,7 +320,10 @@ impl Project {
300320 return Vec :: new ( ) ;
301321 }
302322
303- check_file_impl ( db, file) . iter ( ) . map ( Clone :: clone) . collect ( )
323+ match check_file_impl ( db, file) {
324+ Ok ( diagnostics) => diagnostics. to_vec ( ) ,
325+ Err ( diagnostic) => vec ! [ diagnostic. clone( ) ] ,
326+ }
304327 }
305328
306329 /// Opens a file in the project.
@@ -484,22 +507,19 @@ impl Project {
484507 }
485508}
486509
487- #[ salsa:: tracked( returns( deref ) , heap_size=get_size2:: GetSize :: get_heap_size) ]
488- pub ( crate ) fn check_file_impl ( db : & dyn Db , file : File ) -> Box < [ Diagnostic ] > {
510+ #[ salsa:: tracked( returns( ref ) , heap_size=get_size2:: GetSize :: get_heap_size) ]
511+ pub ( crate ) fn check_file_impl ( db : & dyn Db , file : File ) -> Result < Box < [ Diagnostic ] > , Diagnostic > {
489512 let mut diagnostics: Vec < Diagnostic > = Vec :: new ( ) ;
490513
491514 // Abort checking if there are IO errors.
492515 let source = source_text ( db, file) ;
493516
494517 if let Some ( read_error) = source. read_error ( ) {
495- diagnostics. push (
496- IOErrorDiagnostic {
497- file : Some ( file) ,
498- error : read_error. clone ( ) . into ( ) ,
499- }
500- . to_diagnostic ( ) ,
501- ) ;
502- return diagnostics. into_boxed_slice ( ) ;
518+ return Err ( IOErrorDiagnostic {
519+ file : Some ( file) ,
520+ error : read_error. clone ( ) . into ( ) ,
521+ }
522+ . to_diagnostic ( ) ) ;
503523 }
504524
505525 let parsed = parsed_module ( db, file) ;
@@ -529,13 +549,6 @@ pub(crate) fn check_file_impl(db: &dyn Db, file: File) -> Box<[Diagnostic]> {
529549 }
530550 }
531551
532- if !db. project ( ) . open_fileset ( db) . contains ( & file) {
533- // Drop the AST now that we are done checking this file. It is not currently open,
534- // so it is unlikely to be accessed again soon. If any queries need to access the AST
535- // from across files, it will be re-parsed.
536- parsed. clear ( ) ;
537- }
538-
539552 diagnostics. sort_unstable_by_key ( |diagnostic| {
540553 diagnostic
541554 . primary_span ( )
@@ -544,7 +557,7 @@ pub(crate) fn check_file_impl(db: &dyn Db, file: File) -> Box<[Diagnostic]> {
544557 . start ( )
545558 } ) ;
546559
547- diagnostics. into_boxed_slice ( )
560+ Ok ( diagnostics. into_boxed_slice ( ) )
548561}
549562
550563#[ derive( Debug ) ]
@@ -762,10 +775,11 @@ mod tests {
762775 assert_eq ! ( source_text( & db, file) . as_str( ) , "" ) ;
763776 assert_eq ! (
764777 check_file_impl( & db, file)
765- . iter( )
766- . map( |diagnostic| diagnostic. primary_message( ) . to_string( ) )
767- . collect:: <Vec <_>>( ) ,
768- vec![ "Failed to read file: No such file or directory" . to_string( ) ]
778+ . as_ref( )
779+ . unwrap_err( )
780+ . primary_message( )
781+ . to_string( ) ,
782+ "Failed to read file: No such file or directory" . to_string( )
769783 ) ;
770784
771785 let events = db. take_salsa_events ( ) ;
@@ -778,6 +792,8 @@ mod tests {
778792 assert_eq ! ( source_text( & db, file) . as_str( ) , "" ) ;
779793 assert_eq ! (
780794 check_file_impl( & db, file)
795+ . as_ref( )
796+ . unwrap( )
781797 . iter( )
782798 . map( |diagnostic| diagnostic. primary_message( ) . to_string( ) )
783799 . collect:: <Vec <_>>( ) ,
0 commit comments