Skip to main content

typr_core/components/context/
fingerprint.rs

1#![allow(dead_code, unused_variables, unused_imports)]
2//! Deterministic fingerprint of a typing `Context`.
3//!
4//! Used as one half of the per-module incremental cache key (see
5//! `processes::type_checking::module_cache`): it captures "everything
6//! type-checked before this point", so any upstream change — a new binding,
7//! a different module type, a shifted alias counter — cascades into a
8//! different fingerprint and invalidates downstream cached modules.
9//!
10//! Determinism contract: for the same compilation prefix (same binary, same
11//! sources in the same order), the fingerprint must be identical across
12//! processes. `IndexSet`/`Vec` fields iterate in insertion order and can be
13//! hashed as-is; anything `HashMap`/`HashSet`-backed must be sorted first
14//! (or, for the subtype graph, restricted to its ordered node tree). Do not
15//! add a field here without checking its iteration order.
16
17use crate::components::context::Context;
18use std::fmt::Write as _;
19use std::hash::Hasher;
20
21/// `fmt::Write` adapter feeding formatted bytes straight into a `Hasher`,
22/// so the (large) context never has to be rendered into one big `String`.
23struct HashWriter<'a, H: Hasher>(&'a mut H);
24
25impl<H: Hasher> std::fmt::Write for HashWriter<'_, H> {
26    fn write_str(&mut self, s: &str) -> std::fmt::Result {
27        self.0.write(s.as_bytes());
28        Ok(())
29    }
30}
31
32impl Context {
33    /// Order-stable hash of everything in the context that can influence how
34    /// a subsequent expression is typed or transpiled.
35    pub fn fingerprint(&self) -> u64 {
36        let mut hasher = std::collections::hash_map::DefaultHasher::new();
37        let mut w = HashWriter(&mut hasher);
38
39        // VarType: IndexSets, insertion-ordered.
40        for pair in self.typing_context.variables.iter() {
41            let _ = write!(w, "v{:?}", pair);
42        }
43        for pair in self.typing_context.aliases.iter() {
44            let _ = write!(w, "a{:?}", pair);
45        }
46        for pair in self.typing_context.std.iter() {
47            let _ = write!(w, "s{:?}", pair);
48        }
49        // alias_counter is HashMap-backed (and #[serde(skip)], but it drives
50        // RecordN/ArrayN numbering): sort before hashing.
51        let mut counter: Vec<String> = self
52            .typing_context
53            .alias_counter
54            .clone()
55            .into_iter()
56            .map(|(category, count)| format!("{:?}={}", category, count))
57            .collect();
58        counter.sort();
59        let _ = write!(w, "c{:?}", counter);
60
61        // Subtype graph: ordered node tree only (see Graph::structure_debug).
62        let _ = write!(w, "g{}", self.subtypes.structure_debug());
63
64        // Vec-backed registries: insertion-ordered.
65        let _ = write!(w, "tc{:?}", self.type_constructors);
66        let _ = write!(w, "ra{:?}", self.record_aliases);
67        let _ = write!(w, "em{:?}", self.embedded_methods);
68        let _ = write!(w, "ef{:?}", self.extern_fns);
69        let _ = write!(w, "if{:?}", self.import_from_fns);
70        let _ = write!(w, "sf{:?}", self.signature_fns);
71        let _ = write!(w, "vf{:?}", self.vectorizable_fns);
72
73        // HashMap-backed: sort.
74        let mut constraints: Vec<String> = self
75            .interface_constraints
76            .iter()
77            .map(|(name, typ)| format!("{}={:?}", name, typ))
78            .collect();
79        constraints.sort();
80        let _ = write!(w, "ic{:?}", constraints);
81
82        let mut processed: Vec<String> = self
83            .processed_modules
84            .iter()
85            .map(|(name, typ)| format!("{}={:?}", name, typ))
86            .collect();
87        processed.sort();
88        let _ = write!(w, "pm{:?}", processed);
89
90        let _ = write!(w, "rc{}", self.rigid_counter);
91        // `config` is private to the parent module; fingerprint is a child
92        // module of `context`, so direct field access is allowed.
93        let _ = write!(w, "cf{:?}", self.config);
94
95        hasher.finish()
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use crate::components::language::var::Var;
103    use crate::utils::builder;
104
105    #[test]
106    fn fingerprint_is_deterministic_for_identical_contexts() {
107        let a = Context::default();
108        let b = Context::default();
109        assert_eq!(a.fingerprint(), b.fingerprint());
110    }
111
112    #[test]
113    fn fingerprint_changes_when_a_binding_is_added() {
114        let base = Context::default();
115        let with_var = base
116            .clone()
117            .push_var_type(Var::from_name("x"), builder::integer_type_default(), &base);
118        assert_ne!(base.fingerprint(), with_var.fingerprint());
119    }
120
121    #[test]
122    fn fingerprint_survives_clone() {
123        let base = Context::default();
124        let with_var = base
125            .clone()
126            .push_var_type(Var::from_name("x"), builder::integer_type_default(), &base);
127        assert_eq!(with_var.fingerprint(), with_var.clone().fingerprint());
128    }
129}