@@ -622,7 +622,7 @@ fn build_from_sources_impl(
622622 effective_timeout. map( |t| t. as_secs( ) ) . unwrap_or( 0 ) ) ;
623623
624624 let mut done = 0usize ;
625- let mut rebuilt_set : HashSet < String > = HashSet :: new ( ) ;
625+ let mut export_diffs : HashMap < String , cache :: ExportDiff > = HashMap :: new ( ) ;
626626 let mut cached_count = 0usize ;
627627
628628 for level in & levels {
@@ -635,7 +635,7 @@ fn build_from_sources_impl(
635635 {
636636 let pm = & parsed[ idx] ;
637637 if let Some ( ref mut cache) = cache {
638- if !cache. needs_rebuild ( & pm. module_name , pm. source_hash , & rebuilt_set ) {
638+ if !cache. needs_rebuild_smart ( & pm. module_name , pm. source_hash , & export_diffs ) {
639639 if let Some ( exports) = cache. get_exports ( & pm. module_name ) {
640640 done += 1 ;
641641 cached_count += 1 ;
@@ -647,6 +647,7 @@ fn build_from_sources_impl(
647647 module_results. push ( ModuleResult {
648648 path : pm. path . clone ( ) ,
649649 module_name : pm. module_name . clone ( ) ,
650+
650651 type_errors : vec ! [ ] ,
651652 cached : true ,
652653 } ) ;
@@ -705,24 +706,71 @@ fn build_from_sources_impl(
705706 " [{}/{}] ok: {} ({:.2?})" ,
706707 done, total_modules, pm. module_name, elapsed
707708 ) ;
708- let import_names: Vec < String > = pm. import_parts . iter ( )
709- . map ( |parts| interner:: resolve_module_name ( parts) )
710- . collect ( ) ;
711- let exports_changed = if let Some ( ref mut c) = cache {
712- c. update ( pm. module_name . clone ( ) , pm. source_hash , result. exports . clone ( ) , import_names)
709+ let has_errors = !result. errors . is_empty ( ) ;
710+ if has_errors {
711+ // Don't cache modules with type errors
712+ // Compute a full diff so downstream modules rebuild
713+ let old_exports = cache. as_mut ( ) . and_then ( |c| c. get_exports ( & pm. module_name ) . cloned ( ) ) ;
714+ if let Some ( ref mut c) = cache {
715+ c. remove ( & pm. module_name ) ;
716+ }
717+ // Treat error modules as having all exports changed
718+ let diff = if let Some ( old) = old_exports {
719+ cache:: ExportDiff :: compute ( & old, & result. exports )
720+ } else {
721+ // New module with errors — force downstream rebuild
722+ let mut d = cache:: ExportDiff :: default ( ) ;
723+ d. instances_changed = true ;
724+ d
725+ } ;
726+ if !diff. is_empty ( ) {
727+ log:: debug!(
728+ "[build-plan] {} exports changed: values={:?}, types={:?}, classes={:?}, instances={}, operators={}" ,
729+ pm. module_name, diff. changed_values, diff. changed_types, diff. changed_classes,
730+ diff. instances_changed, diff. operators_changed
731+ ) ;
732+ export_diffs. insert ( pm. module_name . clone ( ) , diff) ;
733+ }
713734 } else {
714- true
715- } ;
716- // Only add to rebuilt_set if exports actually changed
717- if exports_changed {
718- rebuilt_set. insert ( pm. module_name . clone ( ) ) ;
735+ let import_names: Vec < String > = pm. import_parts . iter ( )
736+ . map ( |parts| interner:: resolve_module_name ( parts) )
737+ . collect ( ) ;
738+ let module_ref = pm. module . as_ref ( ) . unwrap ( ) ;
739+ let import_items = cache:: extract_import_items ( & module_ref. imports ) ;
740+ // Get old exports before updating for diff computation
741+ let old_exports = cache. as_mut ( ) . and_then ( |c| c. get_exports ( & pm. module_name ) . cloned ( ) ) ;
742+ let exports_changed = if let Some ( ref mut c) = cache {
743+ c. update ( pm. module_name . clone ( ) , pm. source_hash , result. exports . clone ( ) , import_names, import_items)
744+ } else {
745+ true
746+ } ;
747+ // Compute per-symbol diff for smart rebuild
748+ if exports_changed {
749+ let diff = if let Some ( old) = old_exports {
750+ cache:: ExportDiff :: compute ( & old, & result. exports )
751+ } else {
752+ // New module — force downstream rebuild
753+ let mut d = cache:: ExportDiff :: default ( ) ;
754+ d. instances_changed = true ;
755+ d
756+ } ;
757+ if !diff. is_empty ( ) {
758+ log:: debug!(
759+ "[build-plan] {} exports changed: values={:?}, types={:?}, classes={:?}, instances={}, operators={}" ,
760+ pm. module_name, diff. changed_values, diff. changed_types, diff. changed_classes,
761+ diff. instances_changed, diff. operators_changed
762+ ) ;
763+ export_diffs. insert ( pm. module_name . clone ( ) , diff) ;
764+ }
765+ }
719766 }
720767 // Register exports immediately — result.exports is moved,
721768 // then result (with its types HashMap) is dropped.
722769 registry. register ( & pm. module_parts , result. exports ) ;
723770 module_results. push ( ModuleResult {
724771 path : pm. path . clone ( ) ,
725772 module_name : pm. module_name . clone ( ) ,
773+
726774 type_errors : result. errors ,
727775 cached : false ,
728776 } ) ;
@@ -746,7 +794,7 @@ fn build_from_sources_impl(
746794 for & idx in level. iter ( ) {
747795 let pm = & parsed[ idx] ;
748796 if let Some ( ref mut cache) = cache {
749- if !cache. needs_rebuild ( & pm. module_name , pm. source_hash , & rebuilt_set ) {
797+ if !cache. needs_rebuild_smart ( & pm. module_name , pm. source_hash , & export_diffs ) {
750798 if let Some ( exports) = cache. get_exports ( & pm. module_name ) {
751799 done += 1 ;
752800 cached_count += 1 ;
@@ -832,21 +880,63 @@ fn build_from_sources_impl(
832880 " [{}/{}] ok: {} ({:.2?})" ,
833881 done, total_modules, pm. module_name, elapsed
834882 ) ;
835- let import_names: Vec < String > = pm. import_parts . iter ( )
836- . map ( |parts| interner:: resolve_module_name ( parts) )
837- . collect ( ) ;
838- let exports_changed = if let Some ( ref mut c) = cache {
839- c. update ( pm. module_name . clone ( ) , pm. source_hash , result. exports . clone ( ) , import_names)
883+ let has_errors = !result. errors . is_empty ( ) ;
884+ if has_errors {
885+ // Don't cache modules with type errors
886+ let old_exports = cache. as_mut ( ) . and_then ( |c| c. get_exports ( & pm. module_name ) . cloned ( ) ) ;
887+ if let Some ( ref mut c) = cache {
888+ c. remove ( & pm. module_name ) ;
889+ }
890+ let diff = if let Some ( old) = old_exports {
891+ cache:: ExportDiff :: compute ( & old, & result. exports )
892+ } else {
893+ let mut d = cache:: ExportDiff :: default ( ) ;
894+ d. instances_changed = true ;
895+ d
896+ } ;
897+ if !diff. is_empty ( ) {
898+ log:: debug!(
899+ "[build-plan] {} exports changed: values={:?}, types={:?}, classes={:?}, instances={}, operators={}" ,
900+ pm. module_name, diff. changed_values, diff. changed_types, diff. changed_classes,
901+ diff. instances_changed, diff. operators_changed
902+ ) ;
903+ export_diffs. insert ( pm. module_name . clone ( ) , diff) ;
904+ }
840905 } else {
841- true
842- } ;
843- if exports_changed {
844- rebuilt_set. insert ( pm. module_name . clone ( ) ) ;
906+ let import_names: Vec < String > = pm. import_parts . iter ( )
907+ . map ( |parts| interner:: resolve_module_name ( parts) )
908+ . collect ( ) ;
909+ let module_ref = pm. module . as_ref ( ) . unwrap ( ) ;
910+ let import_items = cache:: extract_import_items ( & module_ref. imports ) ;
911+ let old_exports = cache. as_mut ( ) . and_then ( |c| c. get_exports ( & pm. module_name ) . cloned ( ) ) ;
912+ let exports_changed = if let Some ( ref mut c) = cache {
913+ c. update ( pm. module_name . clone ( ) , pm. source_hash , result. exports . clone ( ) , import_names, import_items)
914+ } else {
915+ true
916+ } ;
917+ if exports_changed {
918+ let diff = if let Some ( old) = old_exports {
919+ cache:: ExportDiff :: compute ( & old, & result. exports )
920+ } else {
921+ let mut d = cache:: ExportDiff :: default ( ) ;
922+ d. instances_changed = true ;
923+ d
924+ } ;
925+ if !diff. is_empty ( ) {
926+ log:: debug!(
927+ "[build-plan] {} exports changed: values={:?}, types={:?}, classes={:?}, instances={}, operators={}" ,
928+ pm. module_name, diff. changed_values, diff. changed_types, diff. changed_classes,
929+ diff. instances_changed, diff. operators_changed
930+ ) ;
931+ export_diffs. insert ( pm. module_name . clone ( ) , diff) ;
932+ }
933+ }
845934 }
846935 registry. register ( & pm. module_parts , result. exports ) ;
847936 module_results. push ( ModuleResult {
848937 path : pm. path . clone ( ) ,
849938 module_name : pm. module_name . clone ( ) ,
939+
850940 type_errors : result. errors ,
851941 cached : false ,
852942 } ) ;
0 commit comments