Skip to main content

bock_types/
lib.rs

1//! Bock types — type system definitions, inference engine, and trait resolution.
2//!
3//! This crate defines the internal type representation used by all later
4//! compiler passes (type checker, interpreter, code generation). It provides:
5//!
6//! - [`Type`] — the main type algebra
7//! - [`Substitution`] — a type-variable-to-type mapping with path compression
8//! - [`unify`] — Hindley-Milner unification with occurs check
9//! - [`TypeChecker`] — bidirectional type inference engine (T-AIR pass)
10
11use std::collections::HashMap;
12
13pub use bock_air::stubs::EffectRef;
14
15pub mod checker;
16pub use checker::{TypeChecker, TypeEnv};
17
18pub mod traits;
19pub use traits::{
20    check_supertrait_obligations, resolve_impl, resolve_method, ImplId, ImplTable, ResolvedMethod,
21    TraitRef,
22};
23
24pub mod ownership;
25pub use ownership::{analyze_ownership, AIRModule, OwnershipInfo, OwnershipState};
26
27pub mod effects;
28pub use effects::{infer_effects, track_effects, Strictness};
29
30pub mod capabilities;
31pub use capabilities::{compute_capabilities, verify_capabilities, CapabilitySet};
32
33pub mod exports;
34pub use exports::{collect_exports, type_to_type_ref};
35
36pub mod seed_imports;
37pub use seed_imports::{seed_imports, seed_prelude, PRELUDE_FROM_CORE};
38
39pub mod vocab;
40
41// ─── Primitive types ──────────────────────────────────────────────────────────
42
43/// The set of primitive (built-in scalar) types in Bock.
44#[derive(Debug, Clone, PartialEq, Eq, Hash)]
45pub enum PrimitiveType {
46    // Unsized integer / float
47    Int,
48    Float,
49    // Sized integers
50    Int8,
51    Int16,
52    Int32,
53    Int64,
54    Int128,
55    UInt8,
56    UInt16,
57    UInt32,
58    UInt64,
59    // Sized floats
60    Float32,
61    Float64,
62    // Arbitrary precision
63    BigInt,
64    BigFloat,
65    Decimal,
66    // Other scalars
67    Bool,
68    Char,
69    String,
70    Byte,
71    Bytes,
72    // Unit / bottom
73    Void,
74    Never,
75}
76
77impl std::fmt::Display for PrimitiveType {
78    /// Renders the primitive's surface-Bock name (`Int`, `String`, …).
79    ///
80    /// The enum variant names are exactly the surface type names, so the
81    /// `Debug` spelling is reused. Keep this invariant when adding variants.
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        write!(f, "{self:?}")
84    }
85}
86
87// ─── Named (user-defined) types ───────────────────────────────────────────────
88
89/// A user-defined named type (record, enum, class).
90///
91/// The `name` is the fully-qualified identifier, e.g. `"Std.Http.Request"`.
92/// The `args` field carries any type arguments already applied, e.g. for a
93/// generic instantiation `List[Int]` the outer [`Type::Generic`] is used, but
94/// for a non-generic named type `args` is empty.
95#[derive(Debug, Clone, PartialEq)]
96pub struct NamedType {
97    /// Fully-qualified name of the type.
98    pub name: String,
99}
100
101// ─── Generic types ────────────────────────────────────────────────────────────
102
103/// A generic type application: a named type constructor applied to type args.
104///
105/// Examples: `List[Int]`, `Map[String, Int]`.
106#[derive(Debug, Clone, PartialEq)]
107pub struct GenericType {
108    /// The type constructor name (e.g. `"List"`, `"Map"`).
109    pub constructor: String,
110    /// Type arguments (in order).
111    pub args: Vec<Type>,
112}
113
114// ─── Function types ───────────────────────────────────────────────────────────
115
116/// A function type: parameter types, return type, and algebraic-effect set.
117#[derive(Debug, Clone, PartialEq)]
118pub struct FnType {
119    /// Types of the positional parameters.
120    pub params: Vec<Type>,
121    /// Return type.
122    pub ret: Box<Type>,
123    /// Algebraic effects this function may perform.
124    pub effects: Vec<EffectRef>,
125}
126
127// ─── Refined types ────────────────────────────────────────────────────────────
128
129/// A predicate expression in a refined type.
130///
131/// This is intentionally kept as a simple string representation for now; later
132/// passes can elaborate it into a full expression AST.
133#[derive(Debug, Clone, PartialEq)]
134pub struct Predicate {
135    /// Human-readable source of the predicate, e.g. `"1 <= self <= 65535"`.
136    pub source: String,
137}
138
139// ─── Flexible (sketch-mode) types ─────────────────────────────────────────────
140
141/// Structural constraints for a flexible (sketch-mode) type.
142///
143/// In sketch mode Bock infers wide types structurally, narrowing by usage.
144/// This is a placeholder structure; later passes fill in the constraint set.
145#[derive(Debug, Clone, PartialEq, Default)]
146pub struct StructuralConstraints {
147    /// Required field names and their types (may be `Type::TypeVar`).
148    pub fields: Vec<(String, Type)>,
149}
150
151// ─── Type variable identifier ─────────────────────────────────────────────────
152
153/// Unique identifier for a type-inference variable.
154pub type TypeVarId = u32;
155
156// ─── Main type algebra ────────────────────────────────────────────────────────
157
158/// The type of a Bock value.
159///
160/// This enum covers all type-level constructs in the Bock language spec:
161/// primitives, user-defined names, generics, tuples, function types, optional,
162/// result, inference variables, refined types, flexible types, and an error
163/// sentinel for error recovery.
164#[derive(Debug, Clone, PartialEq)]
165pub enum Type {
166    /// A built-in primitive scalar type.
167    Primitive(PrimitiveType),
168    /// A user-defined named type (record, enum, class).
169    Named(NamedType),
170    /// A generic type application: `List[T]`, `Map[K, V]`.
171    Generic(GenericType),
172    /// A tuple type: `(A, B, C)`.
173    Tuple(Vec<Type>),
174    /// A function type: `Fn(Int, Int) -> Int with Log`.
175    Function(FnType),
176    /// An optional type: `T?` / `Optional[T]`.
177    Optional(Box<Type>),
178    /// A result type: `Result[T, E]`.
179    Result(Box<Type>, Box<Type>),
180    /// A type-inference variable.
181    TypeVar(TypeVarId),
182    /// A refined type: base type + predicate.
183    Refined(Box<Type>, Predicate),
184    /// A flexible (sketch-mode) type with structural constraints.
185    Flexible(StructuralConstraints),
186    /// Poison type — used during error recovery. Unifies with anything.
187    Error,
188}
189
190impl std::fmt::Display for Type {
191    /// Renders the type in **surface Bock syntax** for user-facing
192    /// diagnostics: `Int`, `List[Int]`, `(Int, String)`, `Fn(Int) -> Bool`,
193    /// `String?`, `Result[Int, Error]`.
194    ///
195    /// Diagnostics must use this rendering — never `Debug`, which leaks
196    /// internal representations like `Primitive(Int)`. Unsolved inference
197    /// variables and flexible (sketch-mode) types render as `_`; the poison
198    /// type renders as `<error>` (it should not normally reach a message,
199    /// since `Type::Error` unifies with anything).
200    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
201        match self {
202            Type::Primitive(p) => write!(f, "{p}"),
203            Type::Named(n) => write!(f, "{}", n.name),
204            Type::Generic(g) => {
205                write!(f, "{}[", g.constructor)?;
206                for (i, arg) in g.args.iter().enumerate() {
207                    if i > 0 {
208                        write!(f, ", ")?;
209                    }
210                    write!(f, "{arg}")?;
211                }
212                write!(f, "]")
213            }
214            Type::Tuple(elems) => {
215                write!(f, "(")?;
216                for (i, e) in elems.iter().enumerate() {
217                    if i > 0 {
218                        write!(f, ", ")?;
219                    }
220                    write!(f, "{e}")?;
221                }
222                write!(f, ")")
223            }
224            Type::Function(func) => {
225                write!(f, "Fn(")?;
226                for (i, p) in func.params.iter().enumerate() {
227                    if i > 0 {
228                        write!(f, ", ")?;
229                    }
230                    write!(f, "{p}")?;
231                }
232                write!(f, ") -> {}", func.ret)?;
233                if !func.effects.is_empty() {
234                    write!(f, " with ")?;
235                    for (i, e) in func.effects.iter().enumerate() {
236                        if i > 0 {
237                            write!(f, " + ")?;
238                        }
239                        write!(f, "{}", e.name)?;
240                    }
241                }
242                Ok(())
243            }
244            Type::Optional(inner) => write!(f, "{inner}?"),
245            Type::Result(ok, err) => write!(f, "Result[{ok}, {err}]"),
246            Type::TypeVar(_) | Type::Flexible(_) => write!(f, "_"),
247            Type::Refined(base, pred) => write!(f, "{base} where {}", pred.source),
248            Type::Error => write!(f, "<error>"),
249        }
250    }
251}
252
253// ─── TypeError ────────────────────────────────────────────────────────────────
254
255/// An error produced by type unification.
256#[derive(Debug, Clone, PartialEq)]
257pub enum TypeError {
258    /// Two types are not unifiable.
259    Mismatch {
260        /// The first type.
261        left: Type,
262        /// The second type.
263        right: Type,
264    },
265    /// An occurs-check failure: binding a type variable would create an
266    /// infinite type, e.g. `T = List[T]`.
267    OccursCheck {
268        /// The type variable that would become infinite.
269        var: TypeVarId,
270        /// The type that contains `var`.
271        ty: Type,
272    },
273    /// Tuple arity mismatch.
274    TupleArity { expected: usize, found: usize },
275    /// Function arity mismatch.
276    FnArity { expected: usize, found: usize },
277}
278
279impl std::fmt::Display for TypeError {
280    /// User-facing rendering. Types render in surface Bock syntax via
281    /// [`Type`]'s `Display` — never `Debug`.
282    ///
283    /// The `expected`/`found` fields of the arity variants (and the
284    /// `left`/`right` of `Mismatch`) follow [`unify`]'s argument order:
285    /// `left`/`expected` describe the first argument, `right`/`found` the
286    /// second. Callers that have a real expected-vs-found orientation must
287    /// pass the expected type as `unify`'s first argument for this text to
288    /// read correctly.
289    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
290        match self {
291            TypeError::Mismatch { left, right } => {
292                write!(f, "expected `{left}`, found `{right}`")
293            }
294            TypeError::OccursCheck { var: _, ty } => {
295                write!(
296                    f,
297                    "the inferred type would be infinite (`_` occurs in `{ty}`)"
298                )
299            }
300            TypeError::TupleArity { expected, found } => {
301                write!(
302                    f,
303                    "expected a tuple with {expected} elements, found {found}"
304                )
305            }
306            TypeError::FnArity { expected, found } => {
307                write!(
308                    f,
309                    "expected a function taking {expected} parameters, found one taking {found}"
310                )
311            }
312        }
313    }
314}
315
316impl std::error::Error for TypeError {}
317
318// ─── Substitution ─────────────────────────────────────────────────────────────
319
320/// A partial map from [`TypeVarId`]s to [`Type`]s.
321///
322/// Supports path compression: when looking up a chain of variable bindings the
323/// lookup walks to the final concrete type (or unbound variable).
324#[derive(Debug, Clone, Default)]
325pub struct Substitution {
326    map: HashMap<TypeVarId, Type>,
327}
328
329impl Substitution {
330    /// Create an empty substitution.
331    #[must_use]
332    pub fn new() -> Self {
333        Self::default()
334    }
335
336    /// Look up a type variable, following chains of variable bindings until
337    /// a concrete type or an unbound variable is reached (path compression is
338    /// applied eagerly for direct variable-to-variable chains).
339    #[must_use]
340    pub fn lookup(&self, mut id: TypeVarId) -> Type {
341        loop {
342            match self.map.get(&id) {
343                None => return Type::TypeVar(id),
344                Some(Type::TypeVar(next)) => {
345                    id = *next;
346                }
347                Some(ty) => return ty.clone(),
348            }
349        }
350    }
351
352    /// Bind a type variable to a type.
353    ///
354    /// Panics in debug builds if `id` is already bound (callers should check
355    /// via [`lookup`](Self::lookup) before binding).
356    pub fn bind(&mut self, id: TypeVarId, ty: Type) {
357        debug_assert!(
358            !self.map.contains_key(&id),
359            "TypeVar ?{id} is already bound"
360        );
361        self.map.insert(id, ty);
362    }
363
364    /// Apply this substitution to a type, recursively replacing all type
365    /// variables that are bound.
366    #[must_use]
367    pub fn apply(&self, ty: &Type) -> Type {
368        match ty {
369            Type::TypeVar(id) => {
370                let resolved = self.lookup(*id);
371                if resolved == *ty {
372                    resolved
373                } else {
374                    self.apply(&resolved)
375                }
376            }
377            Type::Primitive(_) | Type::Error => ty.clone(),
378            Type::Named(_) => ty.clone(),
379            Type::Generic(g) => Type::Generic(GenericType {
380                constructor: g.constructor.clone(),
381                args: g.args.iter().map(|a| self.apply(a)).collect(),
382            }),
383            Type::Tuple(elems) => Type::Tuple(elems.iter().map(|e| self.apply(e)).collect()),
384            Type::Function(f) => Type::Function(FnType {
385                params: f.params.iter().map(|p| self.apply(p)).collect(),
386                ret: Box::new(self.apply(&f.ret)),
387                effects: f.effects.clone(),
388            }),
389            Type::Optional(inner) => Type::Optional(Box::new(self.apply(inner))),
390            Type::Result(ok, err) => {
391                Type::Result(Box::new(self.apply(ok)), Box::new(self.apply(err)))
392            }
393            Type::Refined(base, pred) => Type::Refined(Box::new(self.apply(base)), pred.clone()),
394            Type::Flexible(constraints) => Type::Flexible(StructuralConstraints {
395                fields: constraints
396                    .fields
397                    .iter()
398                    .map(|(name, ty)| (name.clone(), self.apply(ty)))
399                    .collect(),
400            }),
401        }
402    }
403
404    /// Returns `true` if the type variable `id` is unbound in this substitution.
405    #[must_use]
406    pub fn is_unbound(&self, id: TypeVarId) -> bool {
407        matches!(self.lookup(id), Type::TypeVar(_))
408    }
409}
410
411// ─── Occurs check ─────────────────────────────────────────────────────────────
412
413/// Returns `true` if type variable `id` appears free in `ty` under the given
414/// substitution. Used to prevent binding a variable to a type that contains it.
415fn occurs(id: TypeVarId, ty: &Type, subst: &Substitution) -> bool {
416    match ty {
417        Type::TypeVar(other) => {
418            let resolved = subst.lookup(*other);
419            match resolved {
420                Type::TypeVar(rid) => rid == id,
421                _ => occurs(id, &resolved, subst),
422            }
423        }
424        Type::Primitive(_) | Type::Named(_) | Type::Error => false,
425        Type::Generic(g) => g.args.iter().any(|a| occurs(id, a, subst)),
426        Type::Tuple(elems) => elems.iter().any(|e| occurs(id, e, subst)),
427        Type::Function(f) => {
428            f.params.iter().any(|p| occurs(id, p, subst)) || occurs(id, &f.ret, subst)
429        }
430        Type::Optional(inner) => occurs(id, inner, subst),
431        Type::Result(ok, err) => occurs(id, ok, subst) || occurs(id, err, subst),
432        Type::Refined(base, _) => occurs(id, base, subst),
433        Type::Flexible(c) => c.fields.iter().any(|(_, t)| occurs(id, t, subst)),
434    }
435}
436
437// ─── Unification ──────────────────────────────────────────────────────────────
438
439/// Unify two types under the given substitution, extending the substitution
440/// in place when a type variable is bound.
441///
442/// Follows standard Hindley-Milner rules:
443/// - `Type::Error` unifies with anything (poison — prevents cascading errors).
444/// - Type variables are bound after an occurs check.
445/// - Structural types are unified component-wise.
446///
447/// # Errors
448///
449/// Returns a [`TypeError`] if the types are not unifiable.
450pub fn unify(a: &Type, b: &Type, subst: &mut Substitution) -> Result<(), TypeError> {
451    // Resolve variables before matching.
452    let a = subst.apply(a);
453    let b = subst.apply(b);
454
455    match (&a, &b) {
456        // Error is a poison type that unifies with anything.
457        (Type::Error, _) | (_, Type::Error) => Ok(()),
458
459        // Never is the bottom type — it unifies with anything.
460        (Type::Primitive(PrimitiveType::Never), _) | (_, Type::Primitive(PrimitiveType::Never)) => {
461            Ok(())
462        }
463
464        // Two identical types trivially unify.
465        _ if a == b => Ok(()),
466
467        // TypeVar vs anything: occurs check, then bind.
468        (Type::TypeVar(id), other) | (other, Type::TypeVar(id)) => {
469            let id = *id;
470            if occurs(id, other, subst) {
471                return Err(TypeError::OccursCheck {
472                    var: id,
473                    ty: other.clone(),
474                });
475            }
476            subst.bind(id, other.clone());
477            Ok(())
478        }
479
480        // Primitive vs primitive: already handled by the `a == b` case.
481        // Named vs named: same.
482        // Structural cases:
483        (Type::Optional(a_inner), Type::Optional(b_inner)) => unify(a_inner, b_inner, subst),
484
485        (Type::Result(a_ok, a_err), Type::Result(b_ok, b_err)) => {
486            unify(a_ok, b_ok, subst)?;
487            unify(a_err, b_err, subst)
488        }
489
490        (Type::Tuple(a_elems), Type::Tuple(b_elems)) => {
491            if a_elems.len() != b_elems.len() {
492                return Err(TypeError::TupleArity {
493                    expected: a_elems.len(),
494                    found: b_elems.len(),
495                });
496            }
497            for (ae, be) in a_elems.iter().zip(b_elems.iter()) {
498                unify(ae, be, subst)?;
499            }
500            Ok(())
501        }
502
503        (Type::Function(fa), Type::Function(fb)) => {
504            if fa.params.len() != fb.params.len() {
505                return Err(TypeError::FnArity {
506                    expected: fa.params.len(),
507                    found: fb.params.len(),
508                });
509            }
510            for (ap, bp) in fa.params.iter().zip(fb.params.iter()) {
511                unify(ap, bp, subst)?;
512            }
513            unify(&fa.ret, &fb.ret, subst)
514        }
515
516        (Type::Generic(ga), Type::Generic(gb)) => {
517            if ga.constructor != gb.constructor {
518                return Err(TypeError::Mismatch {
519                    left: a.clone(),
520                    right: b.clone(),
521                });
522            }
523            if ga.args.len() != gb.args.len() {
524                return Err(TypeError::Mismatch {
525                    left: a.clone(),
526                    right: b.clone(),
527                });
528            }
529            for (aa, ba) in ga.args.iter().zip(gb.args.iter()) {
530                unify(aa, ba, subst)?;
531            }
532            Ok(())
533        }
534
535        // Refined types: unify the base types (predicates are not unified).
536        (Type::Refined(base_a, _), Type::Refined(base_b, _)) => unify(base_a, base_b, subst),
537
538        // Named vs Generic with same constructor: Named("Foo") is the
539        // bare (un-parameterized) form of Generic("Foo", [args]).
540        // Treat them as compatible so that values typed as Named can
541        // flow into contexts expecting the Generic form.
542        (Type::Named(nt), Type::Generic(g)) | (Type::Generic(g), Type::Named(nt))
543            if nt.name == g.constructor =>
544        {
545            Ok(())
546        }
547
548        // Everything else is a mismatch.
549        _ => Err(TypeError::Mismatch {
550            left: a.clone(),
551            right: b.clone(),
552        }),
553    }
554}
555
556// ─── Type equality helper ─────────────────────────────────────────────────────
557
558/// Check structural equivalence of two types under a substitution.
559///
560/// Two types are equivalent if [`unify`] succeeds on a fresh scratch
561/// substitution that extends the given one (non-destructively).
562#[must_use]
563pub fn types_equal(a: &Type, b: &Type, subst: &Substitution) -> bool {
564    let mut scratch = subst.clone();
565    unify(a, b, &mut scratch).is_ok()
566}
567
568// ─── Tests ────────────────────────────────────────────────────────────────────
569
570#[cfg(test)]
571mod tests {
572    use super::*;
573
574    fn int() -> Type {
575        Type::Primitive(PrimitiveType::Int)
576    }
577
578    fn bool_ty() -> Type {
579        Type::Primitive(PrimitiveType::Bool)
580    }
581
582    fn string_ty() -> Type {
583        Type::Primitive(PrimitiveType::String)
584    }
585
586    fn var(id: TypeVarId) -> Type {
587        Type::TypeVar(id)
588    }
589
590    // ── Display (surface-syntax rendering for diagnostics) ────────────────────
591
592    #[test]
593    fn type_display_renders_surface_syntax() {
594        assert_eq!(int().to_string(), "Int");
595        assert_eq!(string_ty().to_string(), "String");
596        assert_eq!(
597            Type::Named(NamedType {
598                name: "Point".into()
599            })
600            .to_string(),
601            "Point"
602        );
603        assert_eq!(
604            Type::Generic(GenericType {
605                constructor: "List".into(),
606                args: vec![int()],
607            })
608            .to_string(),
609            "List[Int]"
610        );
611        assert_eq!(
612            Type::Generic(GenericType {
613                constructor: "Map".into(),
614                args: vec![string_ty(), int()],
615            })
616            .to_string(),
617            "Map[String, Int]"
618        );
619        assert_eq!(
620            Type::Tuple(vec![int(), bool_ty()]).to_string(),
621            "(Int, Bool)"
622        );
623        assert_eq!(
624            Type::Function(FnType {
625                params: vec![int()],
626                ret: Box::new(bool_ty()),
627                effects: vec![],
628            })
629            .to_string(),
630            "Fn(Int) -> Bool"
631        );
632        assert_eq!(
633            Type::Function(FnType {
634                params: vec![],
635                ret: Box::new(int()),
636                effects: vec![EffectRef::new("Log")],
637            })
638            .to_string(),
639            "Fn() -> Int with Log"
640        );
641        assert_eq!(Type::Optional(Box::new(string_ty())).to_string(), "String?");
642        assert_eq!(
643            Type::Result(Box::new(int()), Box::new(string_ty())).to_string(),
644            "Result[Int, String]"
645        );
646        // Inference variables and sketch-mode types render as `_`; types
647        // must never leak Debug forms like `Primitive(Int)` to users.
648        assert_eq!(var(7).to_string(), "_");
649        assert_eq!(
650            Type::Flexible(StructuralConstraints::default()).to_string(),
651            "_"
652        );
653    }
654
655    #[test]
656    fn type_error_display_uses_surface_syntax() {
657        let err = TypeError::Mismatch {
658            left: int(),
659            right: string_ty(),
660        };
661        assert_eq!(err.to_string(), "expected `Int`, found `String`");
662        assert!(!err.to_string().contains("Primitive("));
663    }
664
665    // ── Substitution ──────────────────────────────────────────────────────────
666
667    #[test]
668    fn subst_lookup_unbound() {
669        let s = Substitution::new();
670        assert_eq!(s.lookup(0), var(0));
671    }
672
673    #[test]
674    fn subst_bind_and_lookup() {
675        let mut s = Substitution::new();
676        s.bind(0, int());
677        assert_eq!(s.lookup(0), int());
678    }
679
680    #[test]
681    fn subst_chain_lookup() {
682        let mut s = Substitution::new();
683        s.bind(0, var(1));
684        s.bind(1, int());
685        assert_eq!(s.lookup(0), int());
686    }
687
688    #[test]
689    fn subst_apply_nested() {
690        let mut s = Substitution::new();
691        s.bind(0, int());
692        let ty = Type::Optional(Box::new(var(0)));
693        assert_eq!(s.apply(&ty), Type::Optional(Box::new(int())));
694    }
695
696    #[test]
697    fn subst_apply_tuple() {
698        let mut s = Substitution::new();
699        s.bind(0, int());
700        s.bind(1, bool_ty());
701        let ty = Type::Tuple(vec![var(0), var(1)]);
702        assert_eq!(s.apply(&ty), Type::Tuple(vec![int(), bool_ty()]));
703    }
704
705    #[test]
706    fn subst_apply_function() {
707        let mut s = Substitution::new();
708        s.bind(0, int());
709        s.bind(1, bool_ty());
710        let ty = Type::Function(FnType {
711            params: vec![var(0)],
712            ret: Box::new(var(1)),
713            effects: vec![],
714        });
715        let result = s.apply(&ty);
716        assert_eq!(
717            result,
718            Type::Function(FnType {
719                params: vec![int()],
720                ret: Box::new(bool_ty()),
721                effects: vec![],
722            })
723        );
724    }
725
726    // ── Unification: base cases ───────────────────────────────────────────────
727
728    #[test]
729    fn unify_same_primitive() {
730        let mut s = Substitution::new();
731        assert!(unify(&int(), &int(), &mut s).is_ok());
732    }
733
734    #[test]
735    fn unify_different_primitives_fails() {
736        let mut s = Substitution::new();
737        assert!(matches!(
738            unify(&int(), &bool_ty(), &mut s),
739            Err(TypeError::Mismatch { .. })
740        ));
741    }
742
743    #[test]
744    fn unify_error_with_anything() {
745        let mut s = Substitution::new();
746        assert!(unify(&Type::Error, &int(), &mut s).is_ok());
747        assert!(unify(&bool_ty(), &Type::Error, &mut s).is_ok());
748        assert!(unify(&Type::Error, &Type::Error, &mut s).is_ok());
749        assert!(unify(&Type::Error, &var(0), &mut s).is_ok());
750    }
751
752    #[test]
753    fn unify_never_with_anything() {
754        let mut s = Substitution::new();
755        let never = Type::Primitive(PrimitiveType::Never);
756        assert!(unify(&never, &int(), &mut s).is_ok());
757        assert!(unify(&bool_ty(), &never, &mut s).is_ok());
758        assert!(unify(&never, &never, &mut s).is_ok());
759        assert!(unify(&never, &var(10), &mut s).is_ok());
760    }
761
762    // ── Unification: type variables ───────────────────────────────────────────
763
764    #[test]
765    fn unify_var_with_concrete() {
766        let mut s = Substitution::new();
767        assert!(unify(&var(0), &int(), &mut s).is_ok());
768        assert_eq!(s.lookup(0), int());
769    }
770
771    #[test]
772    fn unify_concrete_with_var() {
773        let mut s = Substitution::new();
774        assert!(unify(&int(), &var(0), &mut s).is_ok());
775        assert_eq!(s.lookup(0), int());
776    }
777
778    #[test]
779    fn unify_var_with_var() {
780        let mut s = Substitution::new();
781        assert!(unify(&var(0), &var(1), &mut s).is_ok());
782        // After unification one of the vars points to the other.
783        // Applying to ?0 should give either ?1 or ?0 depending on bind direction,
784        // but both should be "equal" in the sense that applying subst to a type
785        // containing both gives the same result.
786        s.bind(1, int());
787        assert_eq!(s.lookup(0), int());
788    }
789
790    // ── Occurs check ──────────────────────────────────────────────────────────
791
792    #[test]
793    fn occurs_check_prevents_infinite_type() {
794        let mut s = Substitution::new();
795        // T = Optional[T]  →  occurs check failure
796        let ty = Type::Optional(Box::new(var(0)));
797        assert!(matches!(
798            unify(&var(0), &ty, &mut s),
799            Err(TypeError::OccursCheck { var: 0, .. })
800        ));
801    }
802
803    #[test]
804    fn occurs_check_list_generic() {
805        let mut s = Substitution::new();
806        let list_t = Type::Generic(GenericType {
807            constructor: "List".into(),
808            args: vec![var(0)],
809        });
810        assert!(matches!(
811            unify(&var(0), &list_t, &mut s),
812            Err(TypeError::OccursCheck { var: 0, .. })
813        ));
814    }
815
816    // ── Unification: structural types ─────────────────────────────────────────
817
818    #[test]
819    fn unify_optional() {
820        let mut s = Substitution::new();
821        assert!(unify(
822            &Type::Optional(Box::new(var(0))),
823            &Type::Optional(Box::new(int())),
824            &mut s
825        )
826        .is_ok());
827        assert_eq!(s.lookup(0), int());
828    }
829
830    #[test]
831    fn unify_result() {
832        let mut s = Substitution::new();
833        let a = Type::Result(Box::new(var(0)), Box::new(var(1)));
834        let b = Type::Result(Box::new(int()), Box::new(string_ty()));
835        assert!(unify(&a, &b, &mut s).is_ok());
836        assert_eq!(s.lookup(0), int());
837        assert_eq!(s.lookup(1), string_ty());
838    }
839
840    #[test]
841    fn unify_tuple_element_wise() {
842        let mut s = Substitution::new();
843        let a = Type::Tuple(vec![var(0), var(1)]);
844        let b = Type::Tuple(vec![int(), bool_ty()]);
845        assert!(unify(&a, &b, &mut s).is_ok());
846        assert_eq!(s.lookup(0), int());
847        assert_eq!(s.lookup(1), bool_ty());
848    }
849
850    #[test]
851    fn unify_tuple_arity_mismatch() {
852        let mut s = Substitution::new();
853        let a = Type::Tuple(vec![int(), bool_ty()]);
854        let b = Type::Tuple(vec![int()]);
855        assert!(matches!(
856            unify(&a, &b, &mut s),
857            Err(TypeError::TupleArity {
858                expected: 2,
859                found: 1
860            })
861        ));
862    }
863
864    #[test]
865    fn unify_function_types() {
866        let mut s = Substitution::new();
867        let a = Type::Function(FnType {
868            params: vec![var(0)],
869            ret: Box::new(var(1)),
870            effects: vec![],
871        });
872        let b = Type::Function(FnType {
873            params: vec![int()],
874            ret: Box::new(bool_ty()),
875            effects: vec![],
876        });
877        assert!(unify(&a, &b, &mut s).is_ok());
878        assert_eq!(s.lookup(0), int());
879        assert_eq!(s.lookup(1), bool_ty());
880    }
881
882    #[test]
883    fn unify_function_arity_mismatch() {
884        let mut s = Substitution::new();
885        let a = Type::Function(FnType {
886            params: vec![int(), bool_ty()],
887            ret: Box::new(int()),
888            effects: vec![],
889        });
890        let b = Type::Function(FnType {
891            params: vec![int()],
892            ret: Box::new(int()),
893            effects: vec![],
894        });
895        assert!(matches!(
896            unify(&a, &b, &mut s),
897            Err(TypeError::FnArity {
898                expected: 2,
899                found: 1
900            })
901        ));
902    }
903
904    #[test]
905    fn unify_generic_same_constructor() {
906        let mut s = Substitution::new();
907        let a = Type::Generic(GenericType {
908            constructor: "List".into(),
909            args: vec![var(0)],
910        });
911        let b = Type::Generic(GenericType {
912            constructor: "List".into(),
913            args: vec![int()],
914        });
915        assert!(unify(&a, &b, &mut s).is_ok());
916        assert_eq!(s.lookup(0), int());
917    }
918
919    #[test]
920    fn unify_generic_different_constructor_fails() {
921        let mut s = Substitution::new();
922        let a = Type::Generic(GenericType {
923            constructor: "List".into(),
924            args: vec![int()],
925        });
926        let b = Type::Generic(GenericType {
927            constructor: "Set".into(),
928            args: vec![int()],
929        });
930        assert!(matches!(
931            unify(&a, &b, &mut s),
932            Err(TypeError::Mismatch { .. })
933        ));
934    }
935
936    // ── Refined types ─────────────────────────────────────────────────────────
937
938    #[test]
939    fn unify_refined_base_types() {
940        let mut s = Substitution::new();
941        let a = Type::Refined(
942            Box::new(var(0)),
943            Predicate {
944                source: "self > 0".into(),
945            },
946        );
947        let b = Type::Refined(
948            Box::new(int()),
949            Predicate {
950                source: "self >= 0".into(),
951            },
952        );
953        assert!(unify(&a, &b, &mut s).is_ok());
954        assert_eq!(s.lookup(0), int());
955    }
956
957    // ── types_equal ───────────────────────────────────────────────────────────
958
959    #[test]
960    fn types_equal_same() {
961        let s = Substitution::new();
962        assert!(types_equal(&int(), &int(), &s));
963    }
964
965    #[test]
966    fn types_equal_different() {
967        let s = Substitution::new();
968        assert!(!types_equal(&int(), &bool_ty(), &s));
969    }
970
971    #[test]
972    fn types_equal_via_subst() {
973        let mut s = Substitution::new();
974        s.bind(0, int());
975        assert!(types_equal(&var(0), &int(), &s));
976    }
977
978    // ── All PrimitiveType variants construct ──────────────────────────────────
979
980    #[test]
981    fn all_primitive_variants() {
982        let prims = [
983            PrimitiveType::Int,
984            PrimitiveType::Float,
985            PrimitiveType::Int8,
986            PrimitiveType::Int16,
987            PrimitiveType::Int32,
988            PrimitiveType::Int64,
989            PrimitiveType::Int128,
990            PrimitiveType::UInt8,
991            PrimitiveType::UInt16,
992            PrimitiveType::UInt32,
993            PrimitiveType::UInt64,
994            PrimitiveType::Float32,
995            PrimitiveType::Float64,
996            PrimitiveType::BigInt,
997            PrimitiveType::BigFloat,
998            PrimitiveType::Decimal,
999            PrimitiveType::Bool,
1000            PrimitiveType::Char,
1001            PrimitiveType::String,
1002            PrimitiveType::Byte,
1003            PrimitiveType::Bytes,
1004            PrimitiveType::Void,
1005            PrimitiveType::Never,
1006        ];
1007        for p in &prims {
1008            let ty = Type::Primitive(p.clone());
1009            assert!(matches!(ty, Type::Primitive(_)));
1010        }
1011    }
1012}