Skip to content

Commit 460083b

Browse files
committed
Split code
1 parent 14b03f4 commit 460083b

3 files changed

Lines changed: 291 additions & 248 deletions

File tree

crates/vespera_macro/src/schema_macro/file_cache.rs

Lines changed: 11 additions & 248 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,17 @@ use super::circular::CircularAnalysis;
8181
use super::file_lookup::collect_rs_files_recursive;
8282
use 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

Comments
 (0)