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 crate::components::r#type::argument_type::ArgumentType;
19use crate::components::r#type::Type;
20use std::fmt::Write as _;
21use std::hash::Hasher;
22
23/// Deterministic rendering of an `ArgumentType`: mirrors the derived `Debug`
24/// shape but descends through the type so nested `Record`/`Interface`/`RClass`
25/// field sets are rendered in sorted (stable) order.
26fn canonical_arg_debug(arg: &ArgumentType) -> String {
27    let label = format!("{:?}", arg.0);
28    let typ = canonical_type_debug(&arg.1);
29    let embedded = arg.2;
30    let variadic = arg.3;
31    let default = format!("{:?}", arg.4);
32    format!(
33        "ArgumentType({}, {}, {}, {}, {})",
34        label, typ, embedded, variadic, default
35    )
36}
37
38/// Deterministic, order-stable rendering of a `Type`. The derived `Debug`
39/// iterates the `HashSet`-backed fields of `Record`/`Interface`/`RClass` in
40/// per-process random order, which would make any fingerprint over a type
41/// containing them unstable across processes (and defeat the per-module
42/// incremental cache). Variants whose fields are all order-stable (Vec, Box,
43/// primitives, HelpData) delegate to the derived `Debug` unchanged.
44pub fn canonical_type_debug(t: &Type) -> String {
45    match t {
46        Type::Record(fields, h) => {
47            let mut v: Vec<String> = fields.iter().map(canonical_arg_debug).collect();
48            v.sort();
49            format!("Record({:?}, {:?})", v, h)
50        }
51        Type::Interface(fields, h) => {
52            let mut v: Vec<String> = fields.iter().map(canonical_arg_debug).collect();
53            v.sort();
54            format!("Interface({:?}, {:?})", v, h)
55        }
56        Type::RClass(set, h) => {
57            let mut v: Vec<String> = set.iter().cloned().collect();
58            v.sort();
59            format!("RClass({:?}, {:?})", v, h)
60        }
61        Type::Function(args, ret, h) => format!(
62            "Function({:?}, {:?}, {:?})",
63            args.iter().map(canonical_arg_debug).collect::<Vec<_>>(),
64            canonical_type_debug(ret),
65            h
66        ),
67        Type::Vec(vt, size, inner, h) => format!(
68            "Vec({:?}, {:?}, {:?}, {:?})",
69            vt,
70            canonical_type_debug(size),
71            canonical_type_debug(inner),
72            h
73        ),
74        Type::Tuple(elems, h) => format!(
75            "Tuple({:?}, {:?})",
76            elems.iter().map(canonical_type_debug).collect::<Vec<_>>(),
77            h
78        ),
79        Type::Params(ts, h) => format!(
80            "Params({:?}, {:?})",
81            ts.iter().map(canonical_type_debug).collect::<Vec<_>>(),
82            h
83        ),
84        Type::Module(args, names, h) => format!(
85            "Module({:?}, {:?}, {:?})",
86            args.iter().map(canonical_arg_debug).collect::<Vec<_>>(),
87            names,
88            h
89        ),
90        Type::Alias(name, params, opaque, h) => format!(
91            "Alias({:?}, {:?}, {:?}, {:?})",
92            name,
93            params.iter().map(canonical_type_debug).collect::<Vec<_>>(),
94            opaque,
95            h
96        ),
97        Type::Tag(name, body, h) => format!("Tag({:?}, {:?}, {:?})", name, canonical_type_debug(body), h),
98        Type::If(cond, branches, h) => format!(
99            "If({:?}, {:?}, {:?})",
100            canonical_type_debug(cond),
101            branches.iter().map(canonical_type_debug).collect::<Vec<_>>(),
102            h
103        ),
104        Type::Condition(a, b, c, h) => format!(
105            "Condition({:?}, {:?}, {:?}, {:?})",
106            canonical_type_debug(a),
107            canonical_type_debug(b),
108            canonical_type_debug(c),
109            h
110        ),
111        Type::Operator(op, a, b, h) => format!(
112            "Operator({:?}, {:?}, {:?}, {:?})",
113            op,
114            canonical_type_debug(a),
115            canonical_type_debug(b),
116            h
117        ),
118        Type::Multi(body, h) => format!("Multi({:?}, {:?})", canonical_type_debug(body), h),
119        _ => format!("{:?}", t),
120    }
121}
122
123/// `fmt::Write` adapter feeding formatted bytes straight into a `Hasher`,
124/// so the (large) context never has to be rendered into one big `String`.
125struct HashWriter<'a, H: Hasher>(&'a mut H);
126
127impl<H: Hasher> std::fmt::Write for HashWriter<'_, H> {
128    fn write_str(&mut self, s: &str) -> std::fmt::Result {
129        self.0.write(s.as_bytes());
130        Ok(())
131    }
132}
133
134impl Context {
135    /// Order-stable hash of everything in the context that can influence how
136    /// a subsequent expression is typed or transpiled.
137    pub fn fingerprint(&self) -> u64 {
138        let mut hasher = std::collections::hash_map::DefaultHasher::new();
139        let mut w = HashWriter(&mut hasher);
140
141        // VarType: IndexSets, insertion-ordered. Each pair must be rendered
142        // through `canonical_type_debug`: a signature carrying an inlined
143        // `Record`/`Interface`/`RClass` prints its field set in random order
144        // under the derived Debug, which would desynchronize cache keys
145        // between processes (see canonical_type_debug).
146        for pair in self.typing_context.variables.iter() {
147            let _ = write!(w, "v{:?}|{}", pair.0, canonical_type_debug(&pair.1));
148        }
149        for pair in self.typing_context.aliases.iter() {
150            let _ = write!(w, "a{:?}|{}", pair.0, canonical_type_debug(&pair.1));
151        }
152        for pair in self.typing_context.std.iter() {
153            let _ = write!(w, "s{:?}|{}", pair.0, canonical_type_debug(&pair.1));
154        }
155        // alias_counter is HashMap-backed (and #[serde(skip)], but it drives
156        // RecordN/ArrayN numbering): sort before hashing.
157        let mut counter: Vec<String> = self
158            .typing_context
159            .alias_counter
160            .clone()
161            .into_iter()
162            .map(|(category, count)| format!("{:?}={}", category, count))
163            .collect();
164        counter.sort();
165        let _ = write!(w, "c{:?}", counter);
166
167        // Subtype graph: ordered node tree only (see Graph::structure_debug).
168        let _ = write!(w, "g{}", self.subtypes.structure_debug());
169
170        // Vec-backed registries: insertion-ordered. `record_aliases` carries
171        // `Type`s (possibly records) — render canonically.
172        let _ = write!(w, "tc{:?}", self.type_constructors);
173        {
174            let ra: Vec<String> = self
175                .record_aliases
176                .iter()
177                .map(|(name, typ)| format!("{}={}", name, canonical_type_debug(typ)))
178                .collect();
179            let _ = write!(w, "ra{:?}", ra);
180        }
181        let _ = write!(w, "em{:?}", self.embedded_methods);
182        let _ = write!(w, "ef{:?}", self.extern_fns);
183        let _ = write!(w, "if{:?}", self.import_from_fns);
184        let _ = write!(w, "sf{:?}", self.signature_fns);
185        let _ = write!(w, "vf{:?}", self.vectorizable_fns);
186
187        // HashMap-backed: sort.
188        let mut constraints: Vec<String> = self
189            .interface_constraints
190            .iter()
191            .map(|(name, typ)| format!("{}={:?}", name, typ))
192            .collect();
193        constraints.sort();
194        let _ = write!(w, "ic{:?}", constraints);
195
196        let mut processed: Vec<String> = self
197            .processed_modules
198            .iter()
199            .map(|(name, typ)| format!("{}={:?}", name, typ))
200            .collect();
201        processed.sort();
202        let _ = write!(w, "pm{:?}", processed);
203
204        let _ = write!(w, "rc{}", self.rigid_counter);
205        // `config` is private to the parent module; fingerprint is a child
206        // module of `context`, so direct field access is allowed.
207        let _ = write!(w, "cf{:?}", self.config);
208
209        hasher.finish()
210    }
211}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216    use crate::components::language::var::Var;
217    use crate::utils::builder;
218
219    #[test]
220    fn fingerprint_is_deterministic_for_identical_contexts() {
221        let a = Context::default();
222        let b = Context::default();
223        assert_eq!(a.fingerprint(), b.fingerprint());
224    }
225
226    #[test]
227    fn fingerprint_changes_when_a_binding_is_added() {
228        let base = Context::default();
229        let with_var = base
230            .clone()
231            .push_var_type(Var::from_name("x"), builder::integer_type_default(), &base);
232        assert_ne!(base.fingerprint(), with_var.fingerprint());
233    }
234
235    #[test]
236    fn fingerprint_survives_clone() {
237        let base = Context::default();
238        let with_var = base
239            .clone()
240            .push_var_type(Var::from_name("x"), builder::integer_type_default(), &base);
241        assert_eq!(with_var.fingerprint(), with_var.clone().fingerprint());
242    }
243}