Skip to main content

lisette_semantics/cache/
go_stdlib.rs

1use rustc_hash::FxHashMap as HashMap;
2use std::fs;
3use std::path::PathBuf;
4
5use serde::{Deserialize, Serialize};
6use stdlib::get_go_stdlib_typedef;
7
8use super::types::CachedDefinition;
9use super::{COMPILER_VERSION_HASH, GO_STDLIB_HASH};
10use crate::store::Store;
11
12#[derive(Serialize, Deserialize)]
13pub struct GoStdlibCache {
14    pub content_hash: u64,
15    pub compiler_version: u64,
16    pub modules: HashMap<String, GoModuleCache>,
17}
18
19#[derive(Serialize, Deserialize)]
20pub struct GoModuleCache {
21    pub definitions: HashMap<String, CachedDefinition>,
22    /// Go module imports (e.g., `["go:io", "go:sync"]`).
23    pub go_imports: Vec<String>,
24}
25
26fn cache_path() -> Option<PathBuf> {
27    let home = std::env::var("HOME").ok()?;
28    Some(
29        PathBuf::from(home)
30            .join(".lisette")
31            .join("cache")
32            .join(format!(
33                "stdlib_defs_{:x}_compiler_{:x}.bin",
34                GO_STDLIB_HASH & 0xFFFFFF,
35                COMPILER_VERSION_HASH & 0xFFFFFF
36            )),
37    )
38}
39
40pub fn try_load_go_stdlib_cache() -> Option<GoStdlibCache> {
41    let path = cache_path()?;
42    let bytes = fs::read(&path).ok()?;
43    let cache: GoStdlibCache = bincode::deserialize(&bytes).ok()?;
44
45    if cache.content_hash != GO_STDLIB_HASH || cache.compiler_version != COMPILER_VERSION_HASH {
46        let _ = fs::remove_file(&path);
47        return None;
48    }
49
50    Some(cache)
51}
52
53pub fn save_go_stdlib_cache(store: &Store, go_module_ids: &[String]) {
54    let Some(path) = cache_path() else { return };
55
56    let mut modules = HashMap::default();
57    // Go definitions don't reference files, so file_id_to_index is always empty.
58    let empty_file_map = HashMap::default();
59    for module_id in go_module_ids {
60        let Some(module) = store.get_module(module_id) else {
61            continue;
62        };
63        let definitions: HashMap<String, CachedDefinition> = module
64            .definitions
65            .iter()
66            .map(|(name, definition)| {
67                (
68                    name.to_string(),
69                    CachedDefinition::from_definition(definition, &empty_file_map),
70                )
71            })
72            .collect();
73
74        let go_imports = get_go_imports_from_source(module_id);
75
76        modules.insert(
77            module_id.clone(),
78            GoModuleCache {
79                definitions,
80                go_imports,
81            },
82        );
83    }
84
85    let cache = GoStdlibCache {
86        content_hash: GO_STDLIB_HASH,
87        compiler_version: COMPILER_VERSION_HASH,
88        modules,
89    };
90
91    let Ok(bytes) = bincode::serialize(&cache) else {
92        return;
93    };
94
95    if let Some(parent) = path.parent() {
96        let _ = fs::create_dir_all(parent);
97    }
98
99    let temp_path = path.with_extension("bin.tmp");
100    if fs::write(&temp_path, bytes).is_ok() {
101        let _ = fs::rename(&temp_path, &path);
102    }
103}
104
105/// Load a Go module and its transitive deps from cache, recursively.
106pub fn load_cached_go_module(store: &mut Store, module_id: &str, cache: &GoStdlibCache) {
107    if store.is_visited(module_id) {
108        return;
109    }
110
111    let Some(cached) = cache.modules.get(module_id) else {
112        return;
113    };
114
115    // Load transitive deps first
116    let imports = cached.go_imports.clone();
117    for dep in &imports {
118        load_cached_go_module(store, dep, cache);
119    }
120
121    if store.is_visited(module_id) {
122        return; // May have been loaded as a transitive dep of a sibling
123    }
124
125    register_cached_go_module(store, module_id, cached);
126}
127
128fn register_cached_go_module(store: &mut Store, module_id: &str, cached: &GoModuleCache) {
129    store.add_module(module_id);
130    store.mark_visited(module_id);
131
132    // Go modules don't need files registered — they're internal and filtered out
133    // of diagnostic rendering. We use an empty file_ids slice for span restoration
134    // (all spans will get file_id 0, which is fine for Go stdlib).
135    let file_ids: &[u32] = &[];
136
137    let module = store.get_module_mut(module_id).unwrap();
138    for (qualified_name, cached_definition) in &cached.definitions {
139        let definition = cached_definition.to_definition(file_ids);
140        module
141            .definitions
142            .insert(qualified_name.clone().into(), definition);
143    }
144}
145
146/// Extract Go imports from a module's `.d.lis` source without parsing.
147fn get_go_imports_from_source(module_id: &str) -> Vec<String> {
148    let Some(go_pkg) = module_id.strip_prefix("go:") else {
149        return vec![];
150    };
151    let Some(source) = get_go_stdlib_typedef(go_pkg) else {
152        return vec![];
153    };
154    source
155        .lines()
156        .filter_map(|line| {
157            let line = line.trim();
158            let rest = line.strip_prefix("import \"go:")?;
159            let pkg = rest.strip_suffix('"')?;
160            Some(format!("go:{pkg}"))
161        })
162        .collect()
163}