Skip to main content

lisette_semantics/checker/
type_env.rs

1//! Union-find-style binding table for `Type::Var` handles.
2//!
3//! `Type::Var(TypeVarId)` is a handle; the binding (Unbound vs Bound-to-a-Type)
4//! lives here in `entries`, indexed by id. Cloning a `Type` clones just the
5//! handle, so `Type` is a pure value (Clone / Eq / Hash / Serialize friendly)
6//! with no shared mutable state.
7//!
8//! Speculative unification works through the `undo_log`: a fresh log is
9//! pushed when entering speculation, bindings are recorded as they happen,
10//! and on Err the originals are restored in reverse order. On Ok the log is
11//! either discarded (no enclosing speculation) or appended to the parent log
12//! (nested speculation — the bindings are committed to the parent, but still
13//! reversible if the parent fails).
14
15use ecow::EcoString;
16use syntax::types::{Bound, Type, TypeVarId};
17
18#[derive(Debug, Clone)]
19pub enum VarState {
20    Unbound { hint: Option<EcoString> },
21    Bound(Type),
22}
23
24pub struct TypeEnv {
25    entries: Vec<VarState>,
26    /// When Some, bindings performed during the current speculative region
27    /// are logged here as `(id, prior_state)` so they can be reverted.
28    undo_log: Option<Vec<(TypeVarId, VarState)>>,
29}
30
31impl Default for TypeEnv {
32    fn default() -> Self {
33        Self::new()
34    }
35}
36
37impl TypeEnv {
38    pub fn new() -> Self {
39        Self {
40            entries: Vec::new(),
41            undo_log: None,
42        }
43    }
44
45    /// Allocate a fresh unbound variable and return its handle.
46    pub fn fresh(&mut self, hint: Option<EcoString>) -> TypeVarId {
47        let id = TypeVarId(self.entries.len() as u32);
48        self.entries.push(VarState::Unbound { hint });
49        id
50    }
51
52    fn slot(id: TypeVarId) -> usize {
53        debug_assert!(
54            !id.is_reserved(),
55            "TypeEnv should not be queried for reserved ids"
56        );
57        id.0 as usize
58    }
59
60    pub fn state(&self, id: TypeVarId) -> &VarState {
61        &self.entries[Self::slot(id)]
62    }
63
64    pub fn is_unbound(&self, id: TypeVarId) -> bool {
65        if id.is_reserved() {
66            return true;
67        }
68        matches!(self.entries[Self::slot(id)], VarState::Unbound { .. })
69    }
70
71    /// Bind `id` to `ty`. Reserved sentinel ids (ignored/uninferred) are
72    /// silently accepted: they unify with anything without storing anything.
73    pub fn bind(&mut self, id: TypeVarId, ty: Type) {
74        if id.is_reserved() {
75            return;
76        }
77        let slot = Self::slot(id);
78        let old = std::mem::replace(&mut self.entries[slot], VarState::Bound(ty));
79        if let Some(log) = &mut self.undo_log {
80            log.push((id, old));
81        }
82    }
83
84    /// Follow a `Type::Var` chain one step at a time until we reach either
85    /// an unbound variable or a non-Var type.
86    pub fn shallow_resolve(&self, ty: &Type) -> Type {
87        let mut current = ty.clone();
88        loop {
89            match &current {
90                Type::Var { id, .. } if !id.is_reserved() => match &self.entries[Self::slot(*id)] {
91                    VarState::Unbound { .. } => return current,
92                    VarState::Bound(bound) => current = bound.clone(),
93                },
94                _ => return current,
95            }
96        }
97    }
98
99    /// Deep resolve: chase `Type::Var` chains, substitute every bound var with
100    /// its chased value, and recurse into composites. Unbound vars (including
101    /// reserved sentinel ids like `IGNORED`/`UNINFERRED`) are preserved as-is.
102    /// Used both during inference and as the post-inference freeze pass.
103    pub fn resolve(&self, ty: &Type) -> Type {
104        match ty {
105            Type::Var { id, .. } if !id.is_reserved() => match &self.entries[Self::slot(*id)] {
106                VarState::Unbound { .. } => ty.clone(),
107                VarState::Bound(bound) => self.resolve(bound),
108            },
109            Type::Nominal {
110                id,
111                params,
112                underlying_ty,
113            } => Type::Nominal {
114                id: id.clone(),
115                params: params.iter().map(|p| self.resolve(p)).collect(),
116                underlying_ty: underlying_ty.as_ref().map(|u| Box::new(self.resolve(u))),
117            },
118            Type::Compound { kind, args } => Type::Compound {
119                kind: *kind,
120                args: args.iter().map(|a| self.resolve(a)).collect(),
121            },
122            Type::Function {
123                params,
124                param_mutability,
125                bounds,
126                return_type,
127            } => Type::Function {
128                params: params.iter().map(|p| self.resolve(p)).collect(),
129                param_mutability: param_mutability.clone(),
130                bounds: bounds
131                    .iter()
132                    .map(|b| Bound {
133                        param_name: b.param_name.clone(),
134                        generic: self.resolve(&b.generic),
135                        ty: self.resolve(&b.ty),
136                    })
137                    .collect(),
138                return_type: Box::new(self.resolve(return_type)),
139            },
140            Type::Forall { vars, body } => Type::Forall {
141                vars: vars.clone(),
142                body: Box::new(self.resolve(body)),
143            },
144            Type::Tuple(elements) => {
145                Type::Tuple(elements.iter().map(|e| self.resolve(e)).collect())
146            }
147            _ => ty.clone(),
148        }
149    }
150
151    /// Occurs check: does `id` appear anywhere inside `ty` (following Var
152    /// chains but stopping at unbound Vars)?
153    pub fn occurs(&self, id: TypeVarId, ty: &Type) -> bool {
154        match ty {
155            Type::Var { id: other, .. } => {
156                if *other == id {
157                    return true;
158                }
159                if other.is_reserved() {
160                    return false;
161                }
162                match &self.entries[Self::slot(*other)] {
163                    VarState::Unbound { .. } => false,
164                    VarState::Bound(bound) => self.occurs(id, bound),
165                }
166            }
167            Type::Nominal { params, .. } => params.iter().any(|p| self.occurs(id, p)),
168            Type::Compound { args, .. } => args.iter().any(|a| self.occurs(id, a)),
169            Type::Function {
170                params,
171                return_type,
172                ..
173            } => params.iter().any(|p| self.occurs(id, p)) || self.occurs(id, return_type),
174            Type::Forall { body, .. } => self.occurs(id, body),
175            Type::Tuple(elements) => elements.iter().any(|e| self.occurs(id, e)),
176            _ => false,
177        }
178    }
179
180    /// Begin a speculative region. Caller holds the returned handle and
181    /// passes it back to `end_speculation` with the region's outcome.
182    pub fn begin_speculation(&mut self) -> Speculation {
183        let prev = self.undo_log.take();
184        self.undo_log = Some(Vec::new());
185        Speculation { prev }
186    }
187
188    /// End a speculative region. If `is_err`, revert all bindings made
189    /// during the region. Otherwise, either commit them (no enclosing
190    /// region) or append them to the enclosing region's log (so it can
191    /// still revert them if it fails).
192    pub fn end_speculation(&mut self, spec: Speculation, is_err: bool) {
193        let log = self.undo_log.take().expect("speculation log must exist");
194        self.undo_log = spec.prev;
195        if is_err {
196            for (id, original) in log.into_iter().rev() {
197                self.entries[Self::slot(id)] = original;
198            }
199        } else if let Some(parent_log) = &mut self.undo_log {
200            parent_log.extend(log);
201        }
202    }
203}
204
205/// Handle returned by `begin_speculation`, consumed by `end_speculation`.
206/// Not clonable — ensures each region is ended exactly once.
207#[must_use]
208pub struct Speculation {
209    prev: Option<Vec<(TypeVarId, VarState)>>,
210}
211
212/// Extension trait for `Type` giving env-aware resolve convenience methods.
213/// Call-site sugar for `env.resolve(&ty)` written as `ty.resolve_in(&env)`.
214pub trait EnvResolve {
215    fn resolve_in(&self, env: &TypeEnv) -> Type;
216    fn shallow_resolve_in(&self, env: &TypeEnv) -> Type;
217}
218
219impl EnvResolve for Type {
220    fn resolve_in(&self, env: &TypeEnv) -> Type {
221        env.resolve(self)
222    }
223    fn shallow_resolve_in(&self, env: &TypeEnv) -> Type {
224        env.shallow_resolve(self)
225    }
226}