use rustc_hash::FxHashMap as HashMap;
use std::fs;
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use stdlib::get_go_stdlib_typedef;
use super::types::CachedDefinition;
use super::{COMPILER_VERSION_HASH, GO_STDLIB_HASH};
use crate::store::Store;
#[derive(Serialize, Deserialize)]
pub struct GoStdlibCache {
pub content_hash: u64,
pub compiler_version: u64,
pub modules: HashMap<String, GoModuleCache>,
}
#[derive(Serialize, Deserialize)]
pub struct GoModuleCache {
pub definitions: HashMap<String, CachedDefinition>,
pub go_imports: Vec<String>,
}
fn cache_path() -> Option<PathBuf> {
let home = std::env::var("HOME").ok()?;
Some(
PathBuf::from(home)
.join(".lisette")
.join("cache")
.join(format!(
"stdlib_defs_{:x}_compiler_{:x}.bin",
GO_STDLIB_HASH & 0xFFFFFF,
COMPILER_VERSION_HASH & 0xFFFFFF
)),
)
}
pub fn try_load_go_stdlib_cache() -> Option<GoStdlibCache> {
let path = cache_path()?;
let bytes = fs::read(&path).ok()?;
let cache: GoStdlibCache = bincode::deserialize(&bytes).ok()?;
if cache.content_hash != GO_STDLIB_HASH || cache.compiler_version != COMPILER_VERSION_HASH {
let _ = fs::remove_file(&path);
return None;
}
Some(cache)
}
pub fn save_go_stdlib_cache(store: &Store, go_module_ids: &[String]) {
let Some(path) = cache_path() else { return };
let mut modules = HashMap::default();
let empty_file_map = HashMap::default();
for module_id in go_module_ids {
let Some(module) = store.get_module(module_id) else {
continue;
};
let definitions: HashMap<String, CachedDefinition> = module
.definitions
.iter()
.map(|(name, definition)| {
(
name.to_string(),
CachedDefinition::from_definition(definition, &empty_file_map),
)
})
.collect();
let go_imports = get_go_imports_from_source(module_id);
modules.insert(
module_id.clone(),
GoModuleCache {
definitions,
go_imports,
},
);
}
let cache = GoStdlibCache {
content_hash: GO_STDLIB_HASH,
compiler_version: COMPILER_VERSION_HASH,
modules,
};
let Ok(bytes) = bincode::serialize(&cache) else {
return;
};
if let Some(parent) = path.parent() {
let _ = fs::create_dir_all(parent);
}
let temp_path = path.with_extension("bin.tmp");
if fs::write(&temp_path, bytes).is_ok() {
let _ = fs::rename(&temp_path, &path);
}
}
pub fn load_cached_go_module(store: &mut Store, module_id: &str, cache: &GoStdlibCache) {
if store.is_visited(module_id) {
return;
}
let Some(cached) = cache.modules.get(module_id) else {
return;
};
let imports = cached.go_imports.clone();
for dep in &imports {
load_cached_go_module(store, dep, cache);
}
if store.is_visited(module_id) {
return; }
register_cached_go_module(store, module_id, cached);
}
fn register_cached_go_module(store: &mut Store, module_id: &str, cached: &GoModuleCache) {
store.add_module(module_id);
store.mark_visited(module_id);
let file_ids: &[u32] = &[];
let module = store.get_module_mut(module_id).unwrap();
for (qualified_name, cached_definition) in &cached.definitions {
let definition = cached_definition.to_definition(file_ids);
module
.definitions
.insert(qualified_name.clone().into(), definition);
}
}
fn get_go_imports_from_source(module_id: &str) -> Vec<String> {
let Some(go_pkg) = module_id.strip_prefix("go:") else {
return vec![];
};
let Some(source) = get_go_stdlib_typedef(go_pkg) else {
return vec![];
};
source
.lines()
.filter_map(|line| {
let line = line.trim();
let rest = line.strip_prefix("import \"go:")?;
let pkg = rest.strip_suffix('"')?;
Some(format!("go:{pkg}"))
})
.collect()
}