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