@@ -81,6 +81,17 @@ use super::circular::CircularAnalysis;
8181use super :: file_lookup:: collect_rs_files_recursive;
8282use crate :: metadata:: StructMetadata ;
8383
84+ /// Phase-4 path-string resolution caches (struct / FK / module-path / circular
85+ /// lookups), split into the `lookups` sidecar to keep this file within the
86+ /// source-size budget. They share the parent `FILE_CACHE` + the
87+ /// `ensure_file_list` / `get_mtime_cached` helpers via `super::` but operate on
88+ /// a disjoint set of `FileCache` fields.
89+ mod lookups;
90+ pub use lookups:: {
91+ get_circular_analysis, get_fk_column, get_module_path_from_schema_path,
92+ get_struct_from_schema_path,
93+ } ;
94+
8495/// Cached directory walk for a single `src_dir`.
8596///
8697/// `fingerprint` is a SipHash over the sorted `(path, mtime)` pairs of
@@ -629,254 +640,6 @@ pub fn parse_struct_cached(definition: &str) -> Result<syn::ItemStruct, syn::Err
629640 } )
630641}
631642
632- /// Get or compute circular reference analysis, with caching.
633- ///
634- /// The cache key is `(source_module_path_joined, definition)` since the same
635- /// model definition analyzed from the same module context always produces
636- /// the same result.
637- pub fn get_circular_analysis ( source_module_path : & [ String ] , definition : & str ) -> CircularAnalysis {
638- let key = ( source_module_path. join ( "::" ) , definition. to_string ( ) ) ;
639-
640- // The borrow must end before analyzing: analysis re-enters FILE_CACHE.
641- let cached = FILE_CACHE . with ( |cache| cache. borrow ( ) . circular_analysis . get ( & key) . cloned ( ) ) ;
642- if let Some ( result) = cached {
643- FILE_CACHE . with ( |cache| cache. borrow_mut ( ) . circular_cache_hits += 1 ) ;
644- return result;
645- }
646-
647- let result = super :: circular:: analyze_circular_refs ( source_module_path, definition) ;
648-
649- FILE_CACHE . with ( |cache| {
650- cache
651- . borrow_mut ( )
652- . circular_analysis
653- . insert ( key, result. clone ( ) ) ;
654- } ) ;
655-
656- result
657- }
658-
659- /// Re-stamp the path-keyed lookup caches (`struct_lookup`, `fk_column_lookup`)
660- /// to the current epoch.
661- ///
662- /// These caches **deliberately survive epoch bumps** (see the
663- /// `path_lookup_epoch` field): keeping resolved path lookups warm across
664- /// invocations lets repeated `schema_type!` / `#[derive(Schema)]` expansions in
665- /// one crate build share path-resolution work. They key on a schema PATH string
666- /// (not a file), so a cache MISS re-resolves through the lower file-content /
667- /// struct-definition mtime caches; within a single `cargo build` no source file
668- /// changes mid-build, so a surviving entry only ever returns the result a
669- /// re-resolution would produce. The epoch stamp is retained only for
670- /// cache-format / test compatibility.
671- ///
672- /// (A long-lived rust-analyzer proc-macro server therefore keeps a resolved
673- /// entry until the server restarts — the accepted cost of the shared-work
674- /// optimisation. A future mtime-aware path cache could be both warm AND fresh,
675- /// but that is a design change, not a one-line tweak.)
676- fn path_lookup_fingerprint ( cache : & mut FileCache , path_str : & str ) -> u64 {
677- let mut hasher = DefaultHasher :: new ( ) ;
678- path_str. hash ( & mut hasher) ;
679-
680- let Some ( manifest_dir) = get_manifest_dir_inner ( cache) else {
681- return hasher. finish ( ) ;
682- } ;
683- let src_dir = Path :: new ( & manifest_dir) . join ( "src" ) ;
684- src_dir. hash ( & mut hasher) ;
685-
686- let segments: Vec < & str > = path_str
687- . split ( "::" )
688- . map ( str:: trim)
689- . filter ( |s| !s. is_empty ( ) )
690- . filter ( |s| * s != "crate" && * s != "self" && * s != "super" )
691- . collect ( ) ;
692-
693- if segments. len ( ) <= 1 {
694- let files = ensure_file_list ( cache, & src_dir) ;
695- for path in files. iter ( ) {
696- fingerprint_path ( cache, path, & mut hasher) ;
697- }
698- return hasher. finish ( ) ;
699- }
700-
701- let module_segments = & segments[ ..segments. len ( ) - 1 ] ;
702- let joined = module_segments. join ( "/" ) ;
703- let candidates = [
704- src_dir. join ( format ! ( "{joined}.rs" ) ) ,
705- src_dir. join ( format ! ( "{joined}/mod.rs" ) ) ,
706- ] ;
707- for path in & candidates {
708- fingerprint_path ( cache, path, & mut hasher) ;
709- }
710-
711- hasher. finish ( )
712- }
713-
714- fn get_manifest_dir_inner ( cache : & mut FileCache ) -> Option < String > {
715- let epoch = cache. epoch ;
716- if cache. manifest_dir_epoch == epoch
717- && let Some ( ref dir) = cache. manifest_dir
718- {
719- return Some ( dir. clone ( ) ) ;
720- }
721- let dir = std:: env:: var ( "CARGO_MANIFEST_DIR" ) . ok ( ) ;
722- cache. manifest_dir . clone_from ( & dir) ;
723- cache. manifest_dir_epoch = epoch;
724- dir
725- }
726-
727- fn fingerprint_path ( cache : & mut FileCache , path : & Path , hasher : & mut DefaultHasher ) {
728- path. hash ( hasher) ;
729- match get_mtime_cached ( cache, path) {
730- Some ( mtime) => {
731- "mtime:some" . hash ( hasher) ;
732- if let Ok ( duration) = mtime. duration_since ( std:: time:: UNIX_EPOCH ) {
733- duration. as_secs ( ) . hash ( hasher) ;
734- duration. subsec_nanos ( ) . hash ( hasher) ;
735- }
736- }
737- None => "mtime:none" . hash ( hasher) ,
738- }
739- }
740-
741- fn ensure_path_lookup_caches_fresh ( cache : & mut FileCache ) {
742- cache. path_lookup_epoch = cache. epoch ;
743- }
744-
745- /// Get or compute struct lookup by schema path, with caching.
746- ///
747- /// Wraps `find_struct_from_schema_path` with a
748- /// `HashMap<String, Option<Arc<StructMetadata>>>` cache. `None` values
749- /// are cached too (negative cache) to avoid repeated failed lookups.
750- /// The `Arc` makes cache hits O(1) instead of cloning the full struct
751- /// definition text per lookup.
752- ///
753- /// The cache **survives epoch bumps** (see
754- /// [`ensure_path_lookup_caches_fresh`]): entries key on a schema PATH string,
755- /// and a cache MISS re-resolves through the lower file-content /
756- /// struct-definition mtime caches — so within one `cargo build` (no source
757- /// file changes mid-build) a surviving entry only ever returns the result a
758- /// re-resolution would produce, while keeping repeated lookups O(1). A
759- /// long-lived rust-analyzer proc-macro server therefore keeps a resolved
760- /// entry until the server restarts — the documented cost of the shared-work
761- /// optimisation (a future mtime-aware path cache could be warm AND fresh).
762- pub fn get_struct_from_schema_path ( path_str : & str ) -> Option < Arc < StructMetadata > > {
763- // Re-stamp the path-lookup epoch (entries deliberately SURVIVE bumps — see
764- // `ensure_path_lookup_caches_fresh`), then read the cache. The borrow ends
765- // before the lookup below, which re-enters FILE_CACHE.
766- let cached = FILE_CACHE . with ( |cache| {
767- let mut cache = cache. borrow_mut ( ) ;
768- ensure_path_lookup_caches_fresh ( & mut cache) ;
769- let fingerprint = path_lookup_fingerprint ( & mut cache, path_str) ;
770- cache. struct_lookup . get ( path_str) . and_then ( |entry| {
771- if entry. last_epoch_validated == cache. epoch || entry. fingerprint == fingerprint {
772- Some ( entry. value . clone ( ) )
773- } else {
774- None
775- }
776- } )
777- } ) ;
778- if let Some ( result) = cached {
779- FILE_CACHE . with ( |cache| cache. borrow_mut ( ) . struct_lookup_cache_hits += 1 ) ;
780- return result;
781- }
782-
783- let result = super :: file_lookup:: find_struct_from_schema_path ( path_str) . map ( Arc :: new) ;
784-
785- FILE_CACHE . with ( |cache| {
786- let mut cache = cache. borrow_mut ( ) ;
787- let fingerprint = path_lookup_fingerprint ( & mut cache, path_str) ;
788- let epoch = cache. epoch ;
789- cache. struct_lookup . insert (
790- path_str. to_string ( ) ,
791- PathLookupEntry {
792- value : result. clone ( ) ,
793- fingerprint,
794- last_epoch_validated : epoch,
795- } ,
796- ) ;
797- } ) ;
798-
799- result
800- }
801-
802- /// Get or compute FK column lookup, with caching.
803- ///
804- /// Wraps `find_fk_column_from_target_entity` with a `HashMap<(String, String), Option<String>>`
805- /// cache. Negative results (`None`) are cached to avoid repeated file lookups.
806- pub fn get_fk_column ( schema_path : & str , via_rel : & str ) -> Option < String > {
807- let key = ( schema_path. to_string ( ) , via_rel. to_string ( ) ) ;
808-
809- // Re-stamp the path-lookup epoch (entries deliberately SURVIVE bumps — see
810- // `ensure_path_lookup_caches_fresh`), then read this epoch's cache. The
811- // borrow ends before the lookup below, which re-enters FILE_CACHE.
812- let cached = FILE_CACHE . with ( |cache| {
813- let mut cache = cache. borrow_mut ( ) ;
814- ensure_path_lookup_caches_fresh ( & mut cache) ;
815- let fingerprint = path_lookup_fingerprint ( & mut cache, schema_path) ;
816- cache. fk_column_lookup . get ( & key) . and_then ( |entry| {
817- if entry. last_epoch_validated == cache. epoch || entry. fingerprint == fingerprint {
818- Some ( entry. value . clone ( ) )
819- } else {
820- None
821- }
822- } )
823- } ) ;
824- if let Some ( result) = cached {
825- FILE_CACHE . with ( |cache| cache. borrow_mut ( ) . fk_column_cache_hits += 1 ) ;
826- return result;
827- }
828-
829- let result = super :: file_lookup:: find_fk_column_from_target_entity ( schema_path, via_rel) ;
830-
831- FILE_CACHE . with ( |cache| {
832- let mut cache = cache. borrow_mut ( ) ;
833- let fingerprint = path_lookup_fingerprint ( & mut cache, schema_path) ;
834- let epoch = cache. epoch ;
835- cache. fk_column_lookup . insert (
836- key,
837- PathLookupEntry {
838- value : result. clone ( ) ,
839- fingerprint,
840- last_epoch_validated : epoch,
841- } ,
842- ) ;
843- } ) ;
844-
845- result
846- }
847-
848- /// Get or compute module path from schema path, with caching.
849- ///
850- /// Wraps `extract_module_path_from_schema_path` logic with a `HashMap<String, Vec<String>>`
851- /// cache. The `schema_path` TokenStream is stringified once for both cache key and computation,
852- /// avoiding the double `.to_string()` that would occur when calling the uncached function.
853- pub fn get_module_path_from_schema_path ( schema_path : & proc_macro2:: TokenStream ) -> Vec < String > {
854- let path_str = schema_path. to_string ( ) ;
855-
856- let cached = FILE_CACHE . with ( |cache| cache. borrow ( ) . module_path_cache . get ( & path_str) . cloned ( ) ) ;
857- if let Some ( result) = cached {
858- FILE_CACHE . with ( |cache| cache. borrow_mut ( ) . module_path_cache_hits += 1 ) ;
859- return result;
860- }
861-
862- let mut result: Vec < String > = path_str
863- . split ( "::" )
864- . map ( str:: trim)
865- . filter ( |s| !s. is_empty ( ) )
866- . map ( ToString :: to_string)
867- . collect ( ) ;
868- result. pop ( ) ;
869-
870- FILE_CACHE . with ( |cache| {
871- cache
872- . borrow_mut ( )
873- . module_path_cache
874- . insert ( path_str, result. clone ( ) ) ;
875- } ) ;
876-
877- result
878- }
879-
880643/// Print profiling summary to stderr if `VESPERA_PROFILE` env var is set.
881644///
882645/// Call this at the end of macro execution to output cache statistics.
0 commit comments