Skip to main content

lisette_semantics/cache/
prelude.rs

1use rustc_hash::FxHashMap as HashMap;
2use std::fs;
3use std::path::PathBuf;
4
5use serde::{Deserialize, Serialize};
6
7use super::types::CachedDefinition;
8use super::{COMPILER_VERSION_HASH, PRELUDE_HASH};
9use crate::prelude::{PRELUDE_FILE_ID, PRELUDE_MODULE_ID};
10use crate::store::Store;
11
12#[derive(Serialize, Deserialize)]
13pub struct PreludeCache {
14    pub content_hash: u64,
15    pub compiler_version: u64,
16    pub definitions: HashMap<String, CachedDefinition>,
17}
18
19fn cache_file_name() -> &'static str {
20    "prelude_defs.bin"
21}
22
23fn cache_path() -> Option<PathBuf> {
24    let home = std::env::var("HOME").ok()?;
25    Some(
26        PathBuf::from(home)
27            .join(".lisette")
28            .join("cache")
29            .join(cache_file_name()),
30    )
31}
32
33pub fn try_load_prelude_cache() -> Option<PreludeCache> {
34    let path = cache_path()?;
35    let bytes = fs::read(&path).ok()?;
36    let cache: PreludeCache = match bincode::deserialize(&bytes) {
37        Ok(cache) => cache,
38        Err(_) => {
39            let _ = fs::remove_file(&path);
40            return None;
41        }
42    };
43
44    if cache.content_hash != PRELUDE_HASH || cache.compiler_version != COMPILER_VERSION_HASH {
45        let _ = fs::remove_file(&path);
46        return None;
47    }
48
49    Some(cache)
50}
51
52pub fn save_prelude_cache(store: &Store) {
53    let Some(path) = cache_path() else { return };
54
55    let Some(module) = store.get_module(PRELUDE_MODULE_ID) else {
56        return;
57    };
58
59    let empty_file_map = HashMap::default();
60    let definitions: HashMap<String, CachedDefinition> = module
61        .definitions
62        .iter()
63        .map(|(name, definition)| {
64            (
65                name.to_string(),
66                CachedDefinition::from_definition(definition, &empty_file_map),
67            )
68        })
69        .collect();
70
71    let cache = PreludeCache {
72        content_hash: PRELUDE_HASH,
73        compiler_version: COMPILER_VERSION_HASH,
74        definitions,
75    };
76
77    let Ok(bytes) = bincode::serialize(&cache) else {
78        return;
79    };
80
81    let Some(parent) = path.parent() else {
82        return;
83    };
84    let _ = fs::create_dir_all(parent);
85
86    let temp_path = super::global_cache_temp_path(&path);
87    if fs::write(&temp_path, &bytes).is_err() {
88        return;
89    }
90    if fs::rename(&temp_path, &path).is_err() {
91        let _ = fs::remove_file(&temp_path);
92        return;
93    }
94    super::prune_legacy_global_caches(parent, "prelude_defs");
95}
96
97pub fn register_cached_prelude(store: &mut Store, cached: PreludeCache) {
98    store.mark_visited(PRELUDE_MODULE_ID);
99
100    // Register the prelude file for file_id → module_id mapping (needed by diagnostics).
101    // Items are empty since we're loading definitions from cache.
102    use syntax::program::File;
103    store.store_file(
104        PRELUDE_MODULE_ID,
105        File {
106            id: PRELUDE_FILE_ID,
107            module_id: PRELUDE_MODULE_ID.to_string(),
108            name: "prelude.d.lis".to_string(),
109            display_path: "prelude.d.lis".to_string(),
110            source: stdlib::LIS_PRELUDE_SOURCE.to_string(),
111            items: vec![],
112        },
113    );
114
115    let file_ids: &[u32] = &[];
116    let module = store
117        .get_module_mut(PRELUDE_MODULE_ID)
118        .expect("prelude module must be registered before loading cached definitions");
119    for (qualified_name, cached_definition) in cached.definitions {
120        let definition = cached_definition.to_definition(file_ids);
121        module.definitions.insert(qualified_name.into(), definition);
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    #[test]
130    fn cache_file_name_is_stable() {
131        assert_eq!(cache_file_name(), "prelude_defs.bin");
132    }
133}