lisette_semantics/cache/
go_stdlib.rs1use rustc_hash::FxHashMap as HashMap;
2use std::fs;
3use std::path::PathBuf;
4
5use serde::{Deserialize, Serialize};
6use stdlib::{Target, get_go_stdlib_typedef};
7
8use super::types::CachedDefinition;
9use super::{COMPILER_VERSION_HASH, GO_STDLIB_HASH};
10use crate::checker::registration::extract_package_directive;
11use crate::store::Store;
12
13#[derive(Serialize, Deserialize)]
14pub struct GoStdlibCache {
15 pub content_hash: u64,
16 pub compiler_version: u64,
17 pub modules: HashMap<String, GoModuleCache>,
18}
19
20#[derive(Serialize, Deserialize)]
21pub struct GoModuleCache {
22 pub definitions: HashMap<String, CachedDefinition>,
23 pub go_imports: Vec<String>,
25}
26
27fn cache_file_name(target: Target) -> String {
28 format!("stdlib_defs_{}.bin", target.cache_segment())
29}
30
31fn cache_path(target: Target) -> Option<PathBuf> {
32 let home = std::env::var("HOME").ok()?;
33 Some(
34 PathBuf::from(home)
35 .join(".lisette")
36 .join("cache")
37 .join(cache_file_name(target)),
38 )
39}
40
41pub fn try_load_go_stdlib_cache(target: Target) -> Option<GoStdlibCache> {
42 let path = cache_path(target)?;
43 let bytes = fs::read(&path).ok()?;
44 let cache: GoStdlibCache = match bincode::deserialize(&bytes) {
45 Ok(cache) => cache,
46 Err(_) => {
47 let _ = fs::remove_file(&path);
48 return None;
49 }
50 };
51
52 if cache.content_hash != GO_STDLIB_HASH || cache.compiler_version != COMPILER_VERSION_HASH {
53 let _ = fs::remove_file(&path);
54 return None;
55 }
56
57 Some(cache)
58}
59
60pub fn save_go_stdlib_cache(store: &Store, go_module_ids: &[String], target: Target) {
61 let Some(path) = cache_path(target) else {
62 return;
63 };
64
65 let mut modules = HashMap::default();
66 let empty_file_map = HashMap::default();
68 for module_id in go_module_ids {
69 let Some(module) = store.get_module(module_id) else {
70 continue;
71 };
72 let definitions: HashMap<String, CachedDefinition> = module
73 .definitions
74 .iter()
75 .map(|(name, definition)| {
76 (
77 name.to_string(),
78 CachedDefinition::from_definition(definition, &empty_file_map),
79 )
80 })
81 .collect();
82
83 let go_imports = get_go_imports_from_source(module_id, target);
84
85 modules.insert(
86 module_id.clone(),
87 GoModuleCache {
88 definitions,
89 go_imports,
90 },
91 );
92 }
93
94 let cache = GoStdlibCache {
95 content_hash: GO_STDLIB_HASH,
96 compiler_version: COMPILER_VERSION_HASH,
97 modules,
98 };
99
100 let Ok(bytes) = bincode::serialize(&cache) else {
101 return;
102 };
103
104 let Some(parent) = path.parent() else {
105 return;
106 };
107 let _ = fs::create_dir_all(parent);
108
109 let temp_path = super::global_cache_temp_path(&path);
110 if fs::write(&temp_path, &bytes).is_err() {
111 return;
112 }
113 if fs::rename(&temp_path, &path).is_err() {
114 let _ = fs::remove_file(&temp_path);
115 return;
116 }
117 super::prune_legacy_global_caches(parent, "stdlib_defs");
118}
119
120pub fn load_cached_go_module(
122 store: &mut Store,
123 module_id: &str,
124 cache: &GoStdlibCache,
125 target: Target,
126) {
127 if store.is_visited(module_id) {
128 return;
129 }
130
131 let Some(cached) = cache.modules.get(module_id) else {
132 return;
133 };
134
135 let imports = cached.go_imports.clone();
137 for dep in &imports {
138 load_cached_go_module(store, dep, cache, target);
139 }
140
141 if store.is_visited(module_id) {
142 return; }
144
145 register_cached_go_module(store, module_id, cached, target);
146}
147
148fn register_cached_go_module(
149 store: &mut Store,
150 module_id: &str,
151 cached: &GoModuleCache,
152 target: Target,
153) {
154 store.add_module(module_id);
155 store.mark_visited(module_id);
156
157 if let Some(go_pkg) = module_id.strip_prefix("go:")
158 && let Some(source) = get_go_stdlib_typedef(go_pkg, target)
159 && let Some(pkg_name) = extract_package_directive(source)
160 && module_id.rsplit('/').next() != Some(pkg_name.as_str())
161 {
162 store
163 .go_package_names
164 .insert(module_id.to_string(), pkg_name);
165 }
166
167 let file_ids: &[u32] = &[];
171
172 let module = store.get_module_mut(module_id).unwrap();
173 for (qualified_name, cached_definition) in &cached.definitions {
174 let definition = cached_definition.to_definition(file_ids);
175 module
176 .definitions
177 .insert(qualified_name.clone().into(), definition);
178 }
179}
180
181fn get_go_imports_from_source(module_id: &str, target: Target) -> Vec<String> {
183 let Some(go_pkg) = module_id.strip_prefix("go:") else {
184 return vec![];
185 };
186 let Some(source) = get_go_stdlib_typedef(go_pkg, target) else {
187 return vec![];
188 };
189 source
190 .lines()
191 .filter_map(|line| {
192 let line = line.trim();
193 let rest = line.strip_prefix("import \"go:")?;
194 let pkg = rest.strip_suffix('"')?;
195 Some(format!("go:{pkg}"))
196 })
197 .collect()
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203
204 #[test]
205 fn cache_file_name_includes_target_only() {
206 let target = Target::new("darwin", "arm64");
207 assert_eq!(cache_file_name(target), "stdlib_defs_darwin_arm64.bin");
208 }
209}