Skip to main content

bhc_types/
lib.rs

1//! # BHC Type System
2//!
3//! This crate implements the type system for the Basel Haskell Compiler (BHC),
4//! including type representations, type inference, unification, and typeclass
5//! resolution.
6//!
7//! ## Overview
8//!
9//! The BHC type system is based on Hindley-Milner type inference with extensions
10//! for:
11//! - Higher-kinded types
12//! - Type classes with functional dependencies
13//! - Type families
14//! - GADTs (Generalized Algebraic Data Types)
15//! - Rank-N polymorphism (limited)
16//! - **M9 Dependent Types Preview**: Shape-indexed tensors with compile-time
17//!   dimension checking
18//!
19//! ## Core Types
20//!
21//! The type system is built around several key types:
22//!
23//! - [`Ty`]: The main type representation
24//! - [`TyVar`]: Type variables for polymorphism
25//! - [`TyId`]: Unique type identifiers
26//! - [`Kind`]: Kinds for higher-kinded types
27//! - [`Scheme`]: Polymorphic type schemes
28//! - [`TyNat`]: Type-level natural numbers (M9)
29//! - [`TyList`]: Type-level lists for shapes (M9)
30//!
31//! ## Type Inference
32//!
33//! Type inference proceeds in several phases:
34//!
35//! 1. **Constraint generation**: Walk the AST and generate type constraints
36//! 2. **Unification**: Solve constraints to find substitutions
37//! 3. **Generalization**: Generalize types at let-bindings
38//! 4. **Defaulting**: Apply defaulting rules for ambiguous types
39//!
40//! ## Shape-Indexed Tensors (M9)
41//!
42//! BHC supports shape-indexed tensor types for compile-time dimension checking:
43//!
44//! ```text
45//! matmul :: Tensor '[m, k] Float -> Tensor '[k, n] Float -> Tensor '[m, n] Float
46//! ```
47//!
48//! See [`nat`] and [`ty_list`] modules for type-level constructs.
49//!
50//! ## See Also
51//!
52//! - `bhc-hir`: High-level IR that uses these types
53//! - `bhc-core`: Core IR with explicit type annotations
54//! - H26-SPEC Section 4: Type System Specification
55//! - H26-SPEC Section 7: Tensor Model
56
57#![warn(missing_docs)]
58#![warn(clippy::all)]
59#![warn(clippy::pedantic)]
60
61pub mod dyn_tensor;
62pub mod nat;
63pub mod ty_list;
64
65pub use dyn_tensor::{dyn_tensor_of, dyn_tensor_tycon, shape_witness_of, shape_witness_tycon};
66pub use nat::TyNat;
67pub use ty_list::TyList;
68
69use bhc_index::Idx;
70use bhc_intern::Symbol;
71use bhc_span::Span;
72use serde::{Deserialize, Serialize};
73
74/// A unique identifier for types within the type system.
75///
76/// Type IDs are used to reference types in the type environment
77/// and during type inference.
78#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
79pub struct TyId(u32);
80
81impl Idx for TyId {
82    #[allow(clippy::cast_possible_truncation)]
83    fn new(idx: usize) -> Self {
84        Self(idx as u32)
85    }
86
87    fn index(self) -> usize {
88        self.0 as usize
89    }
90}
91
92/// A type variable, used for polymorphic types.
93///
94/// Type variables can be either:
95/// - **Unification variables**: Created during type inference, to be solved
96/// - **Rigid variables**: Bound by forall quantifiers, cannot be unified
97#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
98pub struct TyVar {
99    /// Unique identifier for this type variable.
100    pub id: u32,
101    /// The kind of this type variable.
102    pub kind: Kind,
103}
104
105impl TyVar {
106    /// Creates a new type variable with the given ID and kind.
107    #[must_use]
108    pub fn new(id: u32, kind: Kind) -> Self {
109        Self { id, kind }
110    }
111
112    /// Creates a new type variable with kind `*` (Type).
113    #[must_use]
114    pub fn new_star(id: u32) -> Self {
115        Self::new(id, Kind::Star)
116    }
117}
118
119/// Kinds classify types, just as types classify values.
120///
121/// - `*` (Star): The kind of types that have values (e.g., `Int`, `Bool`)
122/// - `* -> *`: The kind of type constructors (e.g., `Maybe`, `[]`)
123/// - `Constraint`: The kind of type class constraints
124/// - `Nat`: The kind of type-level natural numbers (M9)
125/// - `List k`: The kind of type-level lists with element kind `k` (M9)
126#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
127pub enum Kind {
128    /// `*` - The kind of proper types (types that have values).
129    Star,
130    /// `k1 -> k2` - The kind of type constructors.
131    Arrow(Box<Kind>, Box<Kind>),
132    /// `Constraint` - The kind of type class constraints.
133    Constraint,
134    /// A kind variable, for kind inference.
135    Var(u32),
136
137    // === M9 Dependent Types Preview ===
138
139    /// `Nat` - The kind of type-level natural numbers.
140    ///
141    /// Used for tensor dimensions in shape-indexed types:
142    /// ```text
143    /// Tensor :: [Nat] -> * -> *
144    /// ```
145    Nat,
146
147    /// `List k` - The kind of type-level lists with element kind `k`.
148    ///
149    /// Primarily used as `[Nat]` for tensor shapes:
150    /// ```text
151    /// '[1024, 768] :: [Nat]
152    /// ```
153    List(Box<Kind>),
154}
155
156impl Kind {
157    /// Returns the kind `* -> *`.
158    #[must_use]
159    pub fn star_to_star() -> Self {
160        Self::Arrow(Box::new(Self::Star), Box::new(Self::Star))
161    }
162
163    /// Returns true if this is a simple `*` kind.
164    #[must_use]
165    pub fn is_star(&self) -> bool {
166        matches!(self, Self::Star)
167    }
168
169    /// Returns true if this is a `Nat` kind.
170    #[must_use]
171    pub fn is_nat(&self) -> bool {
172        matches!(self, Self::Nat)
173    }
174
175    /// Returns the kind `[Nat]` for tensor shapes.
176    #[must_use]
177    pub fn nat_list() -> Self {
178        Self::List(Box::new(Self::Nat))
179    }
180
181    /// Returns the kind `[Nat] -> * -> *` for the Tensor type constructor.
182    #[must_use]
183    pub fn tensor_kind() -> Self {
184        // Tensor :: [Nat] -> * -> *
185        Self::Arrow(
186            Box::new(Self::nat_list()),
187            Box::new(Self::star_to_star()),
188        )
189    }
190
191    /// Returns true if this is a list kind.
192    #[must_use]
193    pub fn is_list(&self) -> bool {
194        matches!(self, Self::List(_))
195    }
196
197    /// Returns the element kind if this is a list kind.
198    #[must_use]
199    pub fn list_elem_kind(&self) -> Option<&Kind> {
200        match self {
201            Self::List(k) => Some(k),
202            _ => None,
203        }
204    }
205}
206
207/// The main type representation in BHC.
208///
209/// Types are represented as an algebraic data type with various constructors
210/// for different kinds of types in the Haskell type system.
211#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
212pub enum Ty {
213    /// A type variable (e.g., `a` in `a -> a`).
214    Var(TyVar),
215
216    /// A type constructor (e.g., `Int`, `Maybe`, `[]`).
217    Con(TyCon),
218
219    /// An unboxed primitive type (e.g., `Int#`, `Double#`).
220    /// Used in Numeric Profile for zero-overhead computation.
221    Prim(PrimTy),
222
223    /// Type application (e.g., `Maybe Int`, `Either String`).
224    App(Box<Ty>, Box<Ty>),
225
226    /// Function type (e.g., `Int -> Bool`).
227    /// This is sugar for `App(App(TyCon("->"), a), b)`.
228    Fun(Box<Ty>, Box<Ty>),
229
230    /// Tuple type (e.g., `(Int, Bool, String)`).
231    Tuple(Vec<Ty>),
232
233    /// List type (e.g., `[Int]`).
234    /// This is sugar for `App(TyCon("[]"), Int)`.
235    List(Box<Ty>),
236
237    /// A forall-quantified type (e.g., `forall a. a -> a`).
238    Forall(Vec<TyVar>, Box<Ty>),
239
240    /// An error type, used during type checking to allow recovery.
241    Error,
242
243    // === M9 Dependent Types Preview ===
244
245    /// A type-level natural number (e.g., `1024` in `Tensor '[1024] Float`).
246    ///
247    /// Has kind `Nat`. Used for tensor dimensions.
248    Nat(TyNat),
249
250    /// A type-level list (e.g., `'[1024, 768]` in `Tensor '[1024, 768] Float`).
251    ///
252    /// Has kind `[Nat]` when used for tensor shapes.
253    TyList(TyList),
254}
255
256impl Ty {
257    /// Creates a unit type `()`.
258    #[must_use]
259    pub fn unit() -> Self {
260        Self::Tuple(Vec::new())
261    }
262
263    /// Creates a function type `a -> b`.
264    #[must_use]
265    pub fn fun(from: Ty, to: Ty) -> Self {
266        Self::Fun(Box::new(from), Box::new(to))
267    }
268
269    /// Creates a list type `[a]`.
270    #[must_use]
271    pub fn list(elem: Ty) -> Self {
272        Self::List(Box::new(elem))
273    }
274
275    /// Creates an unboxed `Int#` type (64-bit signed integer).
276    #[must_use]
277    pub fn int_prim() -> Self {
278        Self::Prim(PrimTy::I64)
279    }
280
281    /// Creates an unboxed `Double#` type (64-bit float).
282    #[must_use]
283    pub fn double_prim() -> Self {
284        Self::Prim(PrimTy::F64)
285    }
286
287    /// Creates an unboxed `Float#` type (32-bit float).
288    #[must_use]
289    pub fn float_prim() -> Self {
290        Self::Prim(PrimTy::F32)
291    }
292
293    /// Returns true if this is an unboxed primitive type.
294    #[must_use]
295    pub fn is_prim(&self) -> bool {
296        matches!(self, Self::Prim(_))
297    }
298
299    /// Returns the primitive type if this is a Prim variant.
300    #[must_use]
301    pub fn as_prim(&self) -> Option<PrimTy> {
302        match self {
303            Self::Prim(p) => Some(*p),
304            _ => None,
305        }
306    }
307
308    /// Returns true if this is a function type.
309    #[must_use]
310    pub fn is_fun(&self) -> bool {
311        matches!(self, Self::Fun(_, _))
312    }
313
314    /// Returns true if this is an error type.
315    #[must_use]
316    pub fn is_error(&self) -> bool {
317        matches!(self, Self::Error)
318    }
319
320    /// Returns the free type variables in this type.
321    #[must_use]
322    pub fn free_vars(&self) -> Vec<TyVar> {
323        let mut vars = Vec::new();
324        self.collect_free_vars(&mut vars);
325        vars
326    }
327
328    fn collect_free_vars(&self, vars: &mut Vec<TyVar>) {
329        match self {
330            Self::Var(v) => {
331                if !vars.contains(v) {
332                    vars.push(v.clone());
333                }
334            }
335            Self::Con(_) | Self::Prim(_) | Self::Error => {}
336            Self::App(f, a) | Self::Fun(f, a) => {
337                f.collect_free_vars(vars);
338                a.collect_free_vars(vars);
339            }
340            Self::Tuple(tys) => {
341                for ty in tys {
342                    ty.collect_free_vars(vars);
343                }
344            }
345            Self::List(elem) => elem.collect_free_vars(vars),
346            Self::Forall(bound, body) => {
347                let mut body_vars = Vec::new();
348                body.collect_free_vars(&mut body_vars);
349                for v in body_vars {
350                    if !bound.contains(&v) && !vars.contains(&v) {
351                        vars.push(v);
352                    }
353                }
354            }
355            // M9: Type-level naturals and lists
356            Self::Nat(n) => {
357                for v in n.free_vars() {
358                    if !vars.contains(&v) {
359                        vars.push(v);
360                    }
361                }
362            }
363            Self::TyList(l) => {
364                for v in l.free_vars() {
365                    if !vars.contains(&v) {
366                        vars.push(v);
367                    }
368                }
369            }
370        }
371    }
372
373    /// Returns true if this type contains no unification variables.
374    #[must_use]
375    pub fn is_ground(&self) -> bool {
376        match self {
377            Self::Var(_) => false,
378            Self::Con(_) | Self::Prim(_) | Self::Error => true,
379            Self::App(f, a) | Self::Fun(f, a) => f.is_ground() && a.is_ground(),
380            Self::Tuple(tys) => tys.iter().all(Ty::is_ground),
381            Self::List(elem) => elem.is_ground(),
382            Self::Forall(_, body) => body.is_ground(),
383            Self::Nat(n) => n.is_ground(),
384            Self::TyList(l) => l.is_ground(),
385        }
386    }
387
388    /// Creates a type-level natural literal.
389    #[must_use]
390    pub fn nat_lit(n: u64) -> Self {
391        Self::Nat(TyNat::lit(n))
392    }
393
394    /// Creates a shape type from dimension values.
395    #[must_use]
396    pub fn shape(dims: &[u64]) -> Self {
397        Self::TyList(TyList::shape_from_dims(dims))
398    }
399
400    /// Returns true if this is a type-level natural.
401    #[must_use]
402    pub fn is_nat(&self) -> bool {
403        matches!(self, Self::Nat(_))
404    }
405
406    /// Returns true if this is a type-level list.
407    #[must_use]
408    pub fn is_ty_list(&self) -> bool {
409        matches!(self, Self::TyList(_))
410    }
411
412    /// Returns the type-level natural if this is a Nat variant.
413    #[must_use]
414    pub fn as_nat(&self) -> Option<&TyNat> {
415        match self {
416            Self::Nat(n) => Some(n),
417            _ => None,
418        }
419    }
420
421    /// Returns the type-level list if this is a TyList variant.
422    #[must_use]
423    pub fn as_ty_list(&self) -> Option<&TyList> {
424        match self {
425            Self::TyList(l) => Some(l),
426            _ => None,
427        }
428    }
429}
430
431impl std::fmt::Display for Ty {
432    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
433        match self {
434            Self::Var(v) => write!(f, "t{}", v.id),
435            Self::Con(c) => write!(f, "{}", c.name.as_str()),
436            Self::Prim(p) => write!(f, "{p}"),
437            Self::App(fun, arg) => write!(f, "({fun} {arg})"),
438            Self::Fun(from, to) => write!(f, "({from} -> {to})"),
439            Self::Tuple(tys) => {
440                write!(f, "(")?;
441                for (i, ty) in tys.iter().enumerate() {
442                    if i > 0 {
443                        write!(f, ", ")?;
444                    }
445                    write!(f, "{ty}")?;
446                }
447                write!(f, ")")
448            }
449            Self::List(elem) => write!(f, "[{elem}]"),
450            Self::Forall(vars, body) => {
451                write!(f, "forall")?;
452                for v in vars {
453                    write!(f, " t{}", v.id)?;
454                }
455                write!(f, ". {body}")
456            }
457            Self::Error => write!(f, "<error>"),
458            Self::Nat(n) => write!(f, "{n}"),
459            Self::TyList(l) => write!(f, "{l}"),
460        }
461    }
462}
463
464/// A type constructor with its name and kind.
465#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
466pub struct TyCon {
467    /// The name of the type constructor.
468    pub name: Symbol,
469    /// The kind of the type constructor.
470    pub kind: Kind,
471}
472
473impl TyCon {
474    /// Creates a new type constructor with the given name and kind.
475    #[must_use]
476    pub fn new(name: Symbol, kind: Kind) -> Self {
477        Self { name, kind }
478    }
479}
480
481/// A polymorphic type scheme (forall-quantified type).
482///
483/// A scheme represents a type that may be instantiated with different
484/// type arguments. For example, `forall a. a -> a` can be instantiated
485/// as `Int -> Int` or `Bool -> Bool`.
486#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
487pub struct Scheme {
488    /// The bound type variables.
489    pub vars: Vec<TyVar>,
490    /// The constraints on the type variables.
491    pub constraints: Vec<Constraint>,
492    /// The underlying type.
493    pub ty: Ty,
494}
495
496impl Scheme {
497    /// Creates a monomorphic scheme (no quantified variables).
498    #[must_use]
499    pub fn mono(ty: Ty) -> Self {
500        Self {
501            vars: Vec::new(),
502            constraints: Vec::new(),
503            ty,
504        }
505    }
506
507    /// Creates a polymorphic scheme with the given variables and type.
508    #[must_use]
509    pub fn poly(vars: Vec<TyVar>, ty: Ty) -> Self {
510        Self {
511            vars,
512            constraints: Vec::new(),
513            ty,
514        }
515    }
516
517    /// Creates a qualified scheme with constraints.
518    #[must_use]
519    pub fn qualified(vars: Vec<TyVar>, constraints: Vec<Constraint>, ty: Ty) -> Self {
520        Self {
521            vars,
522            constraints,
523            ty,
524        }
525    }
526
527    /// Returns true if this is a monomorphic scheme.
528    #[must_use]
529    pub fn is_mono(&self) -> bool {
530        self.vars.is_empty()
531    }
532}
533
534/// A type class constraint (e.g., `Eq a`, `Num a`).
535#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
536pub struct Constraint {
537    /// The type class name.
538    pub class: Symbol,
539    /// The type arguments to the constraint.
540    pub args: Vec<Ty>,
541    /// Source location of the constraint.
542    pub span: Span,
543}
544
545impl Constraint {
546    /// Creates a new constraint with a single type argument.
547    #[must_use]
548    pub fn new(class: Symbol, ty: Ty, span: Span) -> Self {
549        Self {
550            class,
551            args: vec![ty],
552            span,
553        }
554    }
555
556    /// Creates a new constraint with multiple type arguments.
557    #[must_use]
558    pub fn new_multi(class: Symbol, args: Vec<Ty>, span: Span) -> Self {
559        Self { class, args, span }
560    }
561}
562
563/// A substitution mapping type variables to types.
564#[derive(Clone, Debug, Default, Serialize, Deserialize)]
565pub struct Subst {
566    /// The mapping from type variable IDs to types.
567    mapping: rustc_hash::FxHashMap<u32, Ty>,
568}
569
570impl Subst {
571    /// Creates an empty substitution.
572    #[must_use]
573    pub fn new() -> Self {
574        Self::default()
575    }
576
577    /// Inserts a mapping from a type variable to a type.
578    pub fn insert(&mut self, var: &TyVar, ty: Ty) {
579        self.mapping.insert(var.id, ty);
580    }
581
582    /// Looks up a type variable in the substitution.
583    #[must_use]
584    pub fn get(&self, var: &TyVar) -> Option<&Ty> {
585        self.mapping.get(&var.id)
586    }
587
588    /// Checks if a type variable has a mapping in this substitution.
589    #[must_use]
590    pub fn contains(&self, var: &TyVar) -> bool {
591        self.mapping.contains_key(&var.id)
592    }
593
594    /// Applies this substitution to a type.
595    #[must_use]
596    pub fn apply(&self, ty: &Ty) -> Ty {
597        match ty {
598            Ty::Var(v) => self.get(v).cloned().unwrap_or_else(|| ty.clone()),
599            Ty::Con(_) | Ty::Prim(_) => ty.clone(),
600            Ty::App(f, a) => Ty::App(Box::new(self.apply(f)), Box::new(self.apply(a))),
601            Ty::Fun(from, to) => Ty::Fun(Box::new(self.apply(from)), Box::new(self.apply(to))),
602            Ty::Tuple(tys) => Ty::Tuple(tys.iter().map(|t| self.apply(t)).collect()),
603            Ty::List(elem) => Ty::List(Box::new(self.apply(elem))),
604            Ty::Forall(vars, body) => {
605                // Don't substitute bound variables
606                let mut inner = self.clone();
607                for v in vars {
608                    inner.mapping.remove(&v.id);
609                }
610                Ty::Forall(vars.clone(), Box::new(inner.apply(body)))
611            }
612            Ty::Error => Ty::Error,
613            // M9: Apply substitution to type-level naturals and lists
614            Ty::Nat(n) => Ty::Nat(self.apply_nat(n)),
615            Ty::TyList(l) => Ty::TyList(self.apply_ty_list(l)),
616        }
617    }
618
619    /// Applies this substitution to a type-level natural.
620    #[must_use]
621    pub fn apply_nat(&self, n: &TyNat) -> TyNat {
622        match n {
623            TyNat::Lit(val) => TyNat::Lit(*val),
624            TyNat::Var(v) => {
625                // Check if this variable is mapped to a Nat type
626                match self.get(v) {
627                    Some(Ty::Nat(replacement)) => replacement.clone(),
628                    Some(_) => n.clone(), // Type mismatch, keep original
629                    None => n.clone(),
630                }
631            }
632            TyNat::Add(a, b) => TyNat::add(self.apply_nat(a), self.apply_nat(b)),
633            TyNat::Mul(a, b) => TyNat::mul(self.apply_nat(a), self.apply_nat(b)),
634        }
635    }
636
637    /// Applies this substitution to a type-level list.
638    #[must_use]
639    pub fn apply_ty_list(&self, l: &TyList) -> TyList {
640        match l {
641            TyList::Nil => TyList::Nil,
642            TyList::Cons(head, tail) => {
643                TyList::cons(self.apply(head), self.apply_ty_list(tail))
644            }
645            TyList::Var(v) => {
646                // Check if this variable is mapped to a TyList type
647                match self.get(v) {
648                    Some(Ty::TyList(replacement)) => replacement.clone(),
649                    Some(_) => l.clone(), // Type mismatch, keep original
650                    None => l.clone(),
651                }
652            }
653            TyList::Append(xs, ys) => {
654                TyList::append(self.apply_ty_list(xs), self.apply_ty_list(ys))
655            }
656        }
657    }
658
659    /// Composes two substitutions: (self . other)
660    #[must_use]
661    pub fn compose(&self, other: &Self) -> Self {
662        let mut result = Self::new();
663        for (k, v) in &other.mapping {
664            result.mapping.insert(*k, self.apply(v));
665        }
666        for (k, v) in &self.mapping {
667            result.mapping.entry(*k).or_insert_with(|| v.clone());
668        }
669        result
670    }
671
672    /// Returns true if this substitution is empty.
673    #[must_use]
674    pub fn is_empty(&self) -> bool {
675        self.mapping.is_empty()
676    }
677}
678
679/// Unboxed primitive types for the Numeric Profile.
680///
681/// These types represent machine-level primitives that are:
682/// - Unboxed: No heap allocation, stored directly in registers/stack
683/// - Strict: Always fully evaluated, no thunks
684/// - Fixed-size: Known size at compile time
685///
686/// Used in Numeric Profile for zero-overhead numeric computation.
687/// See H26-SPEC Section 6.2 for unboxed type requirements.
688#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
689pub enum PrimTy {
690    /// 32-bit signed integer (Int32#).
691    I32,
692    /// 64-bit signed integer (Int64#, Int#).
693    I64,
694    /// 32-bit unsigned integer (Word32#).
695    U32,
696    /// 64-bit unsigned integer (Word64#, Word#).
697    U64,
698    /// 32-bit IEEE 754 float (Float#).
699    F32,
700    /// 64-bit IEEE 754 double (Double#).
701    F64,
702    /// 8-bit character/byte (Char#).
703    Char,
704    /// Machine-sized pointer (Addr#).
705    Addr,
706}
707
708impl PrimTy {
709    /// Returns the size in bytes of this primitive type.
710    #[must_use]
711    pub const fn size_bytes(self) -> usize {
712        match self {
713            Self::I32 | Self::U32 | Self::F32 => 4,
714            Self::I64 | Self::U64 | Self::F64 | Self::Addr => 8,
715            Self::Char => 4, // Unicode code point
716        }
717    }
718
719    /// Returns the alignment requirement in bytes.
720    #[must_use]
721    pub const fn alignment(self) -> usize {
722        self.size_bytes()
723    }
724
725    /// Returns the GHC-style name for this primitive type.
726    #[must_use]
727    pub const fn name(self) -> &'static str {
728        match self {
729            Self::I32 => "Int32#",
730            Self::I64 => "Int#",
731            Self::U32 => "Word32#",
732            Self::U64 => "Word#",
733            Self::F32 => "Float#",
734            Self::F64 => "Double#",
735            Self::Char => "Char#",
736            Self::Addr => "Addr#",
737        }
738    }
739
740    /// Returns true if this is a signed integer type.
741    #[must_use]
742    pub const fn is_signed_int(self) -> bool {
743        matches!(self, Self::I32 | Self::I64)
744    }
745
746    /// Returns true if this is an unsigned integer type.
747    #[must_use]
748    pub const fn is_unsigned_int(self) -> bool {
749        matches!(self, Self::U32 | Self::U64)
750    }
751
752    /// Returns true if this is a floating-point type.
753    #[must_use]
754    pub const fn is_float(self) -> bool {
755        matches!(self, Self::F32 | Self::F64)
756    }
757
758    /// Returns true if this is a numeric type.
759    #[must_use]
760    pub const fn is_numeric(self) -> bool {
761        matches!(
762            self,
763            Self::I32 | Self::I64 | Self::U32 | Self::U64 | Self::F32 | Self::F64
764        )
765    }
766}
767
768impl std::fmt::Display for PrimTy {
769    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
770        write!(f, "{}", self.name())
771    }
772}
773
774/// Errors that can occur during type operations.
775#[derive(Clone, Debug, thiserror::Error, Serialize, Deserialize)]
776pub enum TypeError {
777    /// Unification failed between two types.
778    #[error("type mismatch: expected {expected}, found {found}")]
779    Mismatch {
780        /// The expected type.
781        expected: String,
782        /// The found type.
783        found: String,
784        /// The location of the error.
785        span: Span,
786    },
787
788    /// Occurs check failed (infinite type).
789    #[error("infinite type: {var} occurs in {ty}")]
790    OccursCheck {
791        /// The type variable.
792        var: String,
793        /// The type containing the variable.
794        ty: String,
795        /// The location of the error.
796        span: Span,
797    },
798
799    /// Unbound type variable.
800    #[error("unbound type variable: {name}")]
801    UnboundVar {
802        /// The variable name.
803        name: String,
804        /// The location of the error.
805        span: Span,
806    },
807
808    /// Kind mismatch.
809    #[error("kind mismatch: expected {expected}, found {found}")]
810    KindMismatch {
811        /// The expected kind.
812        expected: String,
813        /// The found kind.
814        found: String,
815        /// The location of the error.
816        span: Span,
817    },
818
819    /// Ambiguous type variable.
820    #[error("ambiguous type variable: {var}")]
821    Ambiguous {
822        /// The ambiguous variable.
823        var: String,
824        /// The location of the error.
825        span: Span,
826    },
827}
828
829#[cfg(test)]
830mod tests {
831    use super::*;
832
833    #[test]
834    fn test_ty_free_vars() {
835        let a = TyVar::new_star(0);
836        let b = TyVar::new_star(1);
837
838        // `a -> b` has free vars {a, b}
839        let ty = Ty::fun(Ty::Var(a.clone()), Ty::Var(b.clone()));
840        let vars = ty.free_vars();
841        assert_eq!(vars.len(), 2);
842        assert!(vars.contains(&a));
843        assert!(vars.contains(&b));
844    }
845
846    #[test]
847    fn test_subst_apply() {
848        let a = TyVar::new_star(0);
849        // SAFETY: 0 is a valid symbol index for testing purposes
850        let int_con = TyCon::new(unsafe { Symbol::from_raw(0) }, Kind::Star);
851
852        let mut subst = Subst::new();
853        subst.insert(&a, Ty::Con(int_con.clone()));
854
855        let ty = Ty::fun(Ty::Var(a.clone()), Ty::Var(a));
856        let result = subst.apply(&ty);
857
858        match result {
859            Ty::Fun(from, to) => {
860                assert!(matches!(*from, Ty::Con(_)));
861                assert!(matches!(*to, Ty::Con(_)));
862            }
863            _ => panic!("expected function type"),
864        }
865    }
866
867    #[test]
868    fn test_scheme_mono() {
869        // SAFETY: 0 is a valid symbol index for testing purposes
870        let int_con = TyCon::new(unsafe { Symbol::from_raw(0) }, Kind::Star);
871        let scheme = Scheme::mono(Ty::Con(int_con));
872        assert!(scheme.is_mono());
873    }
874
875    #[test]
876    fn test_prim_ty_properties() {
877        assert_eq!(PrimTy::I64.size_bytes(), 8);
878        assert_eq!(PrimTy::I32.size_bytes(), 4);
879        assert_eq!(PrimTy::F64.size_bytes(), 8);
880        assert_eq!(PrimTy::F32.size_bytes(), 4);
881
882        assert!(PrimTy::I64.is_signed_int());
883        assert!(PrimTy::I32.is_signed_int());
884        assert!(!PrimTy::U64.is_signed_int());
885
886        assert!(PrimTy::F64.is_float());
887        assert!(PrimTy::F32.is_float());
888        assert!(!PrimTy::I64.is_float());
889
890        assert!(PrimTy::I64.is_numeric());
891        assert!(PrimTy::F64.is_numeric());
892        assert!(!PrimTy::Char.is_numeric());
893        assert!(!PrimTy::Addr.is_numeric());
894    }
895
896    #[test]
897    fn test_prim_ty_names() {
898        assert_eq!(PrimTy::I64.name(), "Int#");
899        assert_eq!(PrimTy::F64.name(), "Double#");
900        assert_eq!(PrimTy::F32.name(), "Float#");
901        assert_eq!(PrimTy::Char.name(), "Char#");
902    }
903
904    #[test]
905    fn test_ty_prim_constructors() {
906        let int = Ty::int_prim();
907        assert!(int.is_prim());
908        assert_eq!(int.as_prim(), Some(PrimTy::I64));
909
910        let double = Ty::double_prim();
911        assert!(double.is_prim());
912        assert_eq!(double.as_prim(), Some(PrimTy::F64));
913
914        // Non-prim type
915        let unit = Ty::unit();
916        assert!(!unit.is_prim());
917        assert_eq!(unit.as_prim(), None);
918    }
919
920    #[test]
921    fn test_prim_ty_no_free_vars() {
922        // Primitive types have no free type variables
923        let prim = Ty::int_prim();
924        assert!(prim.free_vars().is_empty());
925    }
926
927    #[test]
928    fn test_subst_prim_unchanged() {
929        // Substitution should leave primitive types unchanged
930        let a = TyVar::new_star(0);
931        let mut subst = Subst::new();
932        subst.insert(&a, Ty::unit());
933
934        let prim = Ty::int_prim();
935        let result = subst.apply(&prim);
936        assert_eq!(result, prim);
937    }
938}