//! Type system for the Lux language #![allow(dead_code)] use std::collections::{HashMap, HashSet}; use std::fmt; use std::sync::atomic::{AtomicUsize, Ordering}; /// Unique ID for type variables static NEXT_TYPE_VAR: AtomicUsize = AtomicUsize::new(0); fn fresh_type_var() -> usize { NEXT_TYPE_VAR.fetch_add(1, Ordering::SeqCst) } /// Internal type representation #[derive(Debug, Clone, PartialEq, Eq)] pub enum Type { /// Type variable (for inference) Var(usize), /// Primitive types Int, Float, Bool, String, Char, Unit, /// Function type with effects and behavioral properties Function { params: Vec, return_type: Box, effects: EffectSet, properties: PropertySet, }, /// Generic type application: List, Option App { constructor: Box, args: Vec, }, /// Named type (user-defined or built-in) Named(String), /// Tuple type Tuple(Vec), /// Record type Record(Vec<(String, Type)>), /// List type (sugar for App(List, [T])) List(Box), /// Option type (sugar for App(Option, [T])) Option(Box), /// Map type (sugar for App(Map, [K, V])) Map(Box, Box), /// Versioned type (e.g., User @v2) Versioned { base: Box, version: VersionInfo, }, /// Error type (for type errors that shouldn't halt compilation) Error, } /// Version information for a versioned type #[derive(Debug, Clone, PartialEq, Eq)] pub enum VersionInfo { /// Exactly this version Exact(u32), /// This version or later AtLeast(u32), /// Latest available version Latest, } impl Type { pub fn function(params: Vec, return_type: Type) -> Self { Type::Function { params, return_type: Box::new(return_type), effects: EffectSet::empty(), properties: PropertySet::empty(), } } pub fn function_with_effects(params: Vec, return_type: Type, effects: EffectSet) -> Self { Type::Function { params, return_type: Box::new(return_type), effects, properties: PropertySet::empty(), } } pub fn function_with_properties( params: Vec, return_type: Type, effects: EffectSet, properties: PropertySet, ) -> Self { Type::Function { params, return_type: Box::new(return_type), effects, properties, } } pub fn var() -> Self { Type::Var(fresh_type_var()) } /// Check if this type contains a type variable pub fn contains_var(&self, var: usize) -> bool { match self { Type::Var(v) => *v == var, Type::Function { params, return_type, .. } => params.iter().any(|p| p.contains_var(var)) || return_type.contains_var(var), Type::App { constructor, args } => { constructor.contains_var(var) || args.iter().any(|a| a.contains_var(var)) } Type::Tuple(elements) => elements.iter().any(|e| e.contains_var(var)), Type::Record(fields) => fields.iter().any(|(_, t)| t.contains_var(var)), Type::List(inner) | Type::Option(inner) => inner.contains_var(var), Type::Map(k, v) => k.contains_var(var) || v.contains_var(var), Type::Versioned { base, .. } => base.contains_var(var), _ => false, } } /// Apply a substitution to this type pub fn apply(&self, subst: &Substitution) -> Type { match self { Type::Var(v) => { if let Some(t) = subst.get(*v) { t.apply(subst) } else { self.clone() } } Type::Function { params, return_type, effects, properties, } => Type::Function { params: params.iter().map(|p| p.apply(subst)).collect(), return_type: Box::new(return_type.apply(subst)), effects: effects.clone(), properties: properties.clone(), }, Type::App { constructor, args } => Type::App { constructor: Box::new(constructor.apply(subst)), args: args.iter().map(|a| a.apply(subst)).collect(), }, Type::Tuple(elements) => Type::Tuple(elements.iter().map(|e| e.apply(subst)).collect()), Type::Record(fields) => Type::Record( fields .iter() .map(|(n, t)| (n.clone(), t.apply(subst))) .collect(), ), Type::List(inner) => Type::List(Box::new(inner.apply(subst))), Type::Option(inner) => Type::Option(Box::new(inner.apply(subst))), Type::Map(k, v) => Type::Map(Box::new(k.apply(subst)), Box::new(v.apply(subst))), Type::Versioned { base, version } => Type::Versioned { base: Box::new(base.apply(subst)), version: version.clone(), }, _ => self.clone(), } } /// Get all free type variables in this type pub fn free_vars(&self) -> HashSet { match self { Type::Var(v) => { let mut set = HashSet::new(); set.insert(*v); set } Type::Function { params, return_type, .. } => { let mut vars = HashSet::new(); for p in params { vars.extend(p.free_vars()); } vars.extend(return_type.free_vars()); vars } Type::App { constructor, args } => { let mut vars = constructor.free_vars(); for a in args { vars.extend(a.free_vars()); } vars } Type::Tuple(elements) => { let mut vars = HashSet::new(); for e in elements { vars.extend(e.free_vars()); } vars } Type::Record(fields) => { let mut vars = HashSet::new(); for (_, t) in fields { vars.extend(t.free_vars()); } vars } Type::List(inner) | Type::Option(inner) => inner.free_vars(), Type::Map(k, v) => { let mut vars = k.free_vars(); vars.extend(v.free_vars()); vars } Type::Versioned { base, .. } => base.free_vars(), _ => HashSet::new(), } } } impl fmt::Display for Type { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Type::Var(v) => write!(f, "?{}", v), Type::Int => write!(f, "Int"), Type::Float => write!(f, "Float"), Type::Bool => write!(f, "Bool"), Type::String => write!(f, "String"), Type::Char => write!(f, "Char"), Type::Unit => write!(f, "Unit"), Type::Function { params, return_type, effects, properties, } => { write!(f, "fn(")?; for (i, p) in params.iter().enumerate() { if i > 0 { write!(f, ", ")?; } write!(f, "{}", p)?; } write!(f, "): {}", return_type)?; if !effects.is_empty() { write!(f, " with {{{}}}", effects)?; } if !properties.is_empty() { write!(f, " {}", properties)?; } Ok(()) } Type::App { constructor, args } => { write!(f, "{}<", constructor)?; for (i, a) in args.iter().enumerate() { if i > 0 { write!(f, ", ")?; } write!(f, "{}", a)?; } write!(f, ">") } Type::Named(name) => write!(f, "{}", name), Type::Tuple(elements) => { write!(f, "(")?; for (i, e) in elements.iter().enumerate() { if i > 0 { write!(f, ", ")?; } write!(f, "{}", e)?; } write!(f, ")") } Type::Record(fields) => { write!(f, "{{ ")?; for (i, (name, typ)) in fields.iter().enumerate() { if i > 0 { write!(f, ", ")?; } write!(f, "{}: {}", name, typ)?; } write!(f, " }}") } Type::List(inner) => write!(f, "List<{}>", inner), Type::Option(inner) => write!(f, "Option<{}>", inner), Type::Map(k, v) => write!(f, "Map<{}, {}>", k, v), Type::Versioned { base, version } => { write!(f, "{} {}", base, version) } Type::Error => write!(f, ""), } } } impl fmt::Display for VersionInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { VersionInfo::Exact(v) => write!(f, "@v{}", v), VersionInfo::AtLeast(v) => write!(f, "@v{}+", v), VersionInfo::Latest => write!(f, "@latest"), } } } /// A set of effects #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct EffectSet { pub effects: HashSet, } impl EffectSet { pub fn empty() -> Self { Self { effects: HashSet::new(), } } pub fn single(effect: impl Into) -> Self { let mut effects = HashSet::new(); effects.insert(effect.into()); Self { effects } } pub fn from_iter(iter: impl IntoIterator) -> Self { Self { effects: iter.into_iter().collect(), } } pub fn is_empty(&self) -> bool { self.effects.is_empty() } pub fn contains(&self, effect: &str) -> bool { self.effects.contains(effect) } pub fn union(&self, other: &EffectSet) -> EffectSet { EffectSet { effects: self.effects.union(&other.effects).cloned().collect(), } } pub fn is_subset(&self, other: &EffectSet) -> bool { self.effects.is_subset(&other.effects) } pub fn insert(&mut self, effect: impl Into) { self.effects.insert(effect.into()); } } impl fmt::Display for EffectSet { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let effects: Vec<_> = self.effects.iter().collect(); for (i, e) in effects.iter().enumerate() { if i > 0 { write!(f, ", ")?; } write!(f, "{}", e)?; } Ok(()) } } /// Set of behavioral properties for a function #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct PropertySet { pub properties: HashSet, } /// A behavioral property (mirrors BehavioralProperty from AST but for type system) #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Property { Pure, Total, Idempotent, Deterministic, Commutative, } impl From for Property { fn from(p: crate::ast::BehavioralProperty) -> Self { match p { crate::ast::BehavioralProperty::Pure => Property::Pure, crate::ast::BehavioralProperty::Total => Property::Total, crate::ast::BehavioralProperty::Idempotent => Property::Idempotent, crate::ast::BehavioralProperty::Deterministic => Property::Deterministic, crate::ast::BehavioralProperty::Commutative => Property::Commutative, } } } impl fmt::Display for Property { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Property::Pure => write!(f, "pure"), Property::Total => write!(f, "total"), Property::Idempotent => write!(f, "idempotent"), Property::Deterministic => write!(f, "deterministic"), Property::Commutative => write!(f, "commutative"), } } } impl PropertySet { pub fn empty() -> Self { Self { properties: HashSet::new(), } } pub fn from_ast(props: &[crate::ast::BehavioralProperty]) -> Self { Self { properties: props.iter().copied().map(Property::from).collect(), } } pub fn is_empty(&self) -> bool { self.properties.is_empty() } pub fn contains(&self, prop: Property) -> bool { self.properties.contains(&prop) } pub fn is_pure(&self) -> bool { self.contains(Property::Pure) } pub fn is_total(&self) -> bool { self.contains(Property::Total) } pub fn insert(&mut self, prop: Property) { self.properties.insert(prop); } /// Check if this property set is a superset of another (satisfies constraints) pub fn satisfies(&self, required: &PropertySet) -> bool { required.properties.is_subset(&self.properties) } /// Intersection of two property sets (for composition) pub fn intersection(&self, other: &PropertySet) -> PropertySet { PropertySet { properties: self .properties .intersection(&other.properties) .copied() .collect(), } } } impl fmt::Display for PropertySet { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let props: Vec<_> = self.properties.iter().collect(); for (i, p) in props.iter().enumerate() { if i > 0 { write!(f, ", ")?; } write!(f, "is {}", p)?; } Ok(()) } } /// Type substitution (mapping from type variables to types) #[derive(Debug, Clone, Default)] pub struct Substitution { mapping: HashMap, } impl Substitution { pub fn new() -> Self { Self { mapping: HashMap::new(), } } pub fn get(&self, var: usize) -> Option<&Type> { self.mapping.get(&var) } pub fn insert(&mut self, var: usize, typ: Type) { self.mapping.insert(var, typ); } pub fn compose(&self, other: &Substitution) -> Substitution { let mut result = Substitution::new(); // Apply self to all mappings in other for (var, typ) in &other.mapping { result.insert(*var, typ.apply(self)); } // Add mappings from self that aren't in other for (var, typ) in &self.mapping { if !result.mapping.contains_key(var) { result.insert(*var, typ.clone()); } } result } } /// Type scheme (polymorphic type) #[derive(Debug, Clone)] pub struct TypeScheme { pub vars: Vec, pub typ: Type, } impl TypeScheme { pub fn mono(typ: Type) -> Self { Self { vars: Vec::new(), typ, } } pub fn poly(vars: Vec, typ: Type) -> Self { Self { vars, typ } } /// Instantiate this scheme with fresh type variables pub fn instantiate(&self) -> Type { let mut subst = Substitution::new(); for &var in &self.vars { subst.insert(var, Type::var()); } self.typ.apply(&subst) } pub fn free_vars(&self) -> HashSet { let mut vars = self.typ.free_vars(); for v in &self.vars { vars.remove(v); } vars } } impl fmt::Display for TypeScheme { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.vars.is_empty() { write!(f, "{}", self.typ) } else { write!(f, "forall ")?; for (i, v) in self.vars.iter().enumerate() { if i > 0 { write!(f, ", ")?; } write!(f, "?{}", v)?; } write!(f, ". {}", self.typ) } } } /// Effect definition #[derive(Debug, Clone)] pub struct EffectDef { pub name: String, pub type_params: Vec, pub operations: Vec, } /// Effect operation definition #[derive(Debug, Clone)] pub struct EffectOpDef { pub name: String, pub params: Vec<(String, Type)>, pub return_type: Type, } /// Type environment #[derive(Debug, Clone, Default)] pub struct TypeEnv { /// Variable bindings pub bindings: HashMap, /// Type definitions pub types: HashMap, /// Effect definitions pub effects: HashMap, /// Handler types pub handlers: HashMap, /// Trait definitions pub traits: HashMap, /// Trait implementations: (trait_name, type) -> impl pub trait_impls: Vec, } /// Type definition #[derive(Debug, Clone)] pub enum TypeDef { Alias(Type), Record(Vec<(String, Type)>), Enum(Vec), } /// Variant definition #[derive(Debug, Clone)] pub struct VariantDef { pub name: String, pub fields: VariantFieldsDef, } /// Variant fields #[derive(Debug, Clone)] pub enum VariantFieldsDef { Unit, Tuple(Vec), Record(Vec<(String, Type)>), } /// Handler definition #[derive(Debug, Clone)] pub struct HandlerDef { pub name: String, pub effect: String, pub params: Vec<(String, Type)>, } /// Trait definition #[derive(Debug, Clone)] pub struct TraitDef { pub name: String, /// Type parameters for the trait (e.g., Functor) pub type_params: Vec, /// Super traits that must be implemented pub super_traits: Vec, /// Method signatures pub methods: Vec, } /// A trait bound in type definitions #[derive(Debug, Clone)] pub struct TraitBoundDef { pub trait_name: String, pub type_args: Vec, } /// A method signature in a trait #[derive(Debug, Clone)] pub struct TraitMethodDef { pub name: String, pub type_params: Vec, pub params: Vec<(String, Type)>, pub return_type: Type, /// Whether this method has a default implementation pub has_default: bool, } /// A trait implementation #[derive(Debug, Clone)] pub struct TraitImpl { pub trait_name: String, pub trait_args: Vec, /// The type this impl is for pub target_type: Type, /// Type parameters on the impl (e.g., impl Show for List) pub type_params: Vec, /// Constraints on type parameters (e.g., where T: Show) pub constraints: Vec<(String, Vec)>, /// Method implementations pub methods: HashMap, } /// A trait constraint on a type variable #[derive(Debug, Clone, PartialEq, Eq)] pub struct TraitConstraintDef { pub type_var: String, pub bounds: Vec, } impl PartialEq for TraitBoundDef { fn eq(&self, other: &Self) -> bool { self.trait_name == other.trait_name && self.type_args == other.type_args } } impl Eq for TraitBoundDef {} impl TypeEnv { pub fn new() -> Self { Self::default() } /// Create environment with built-in types pub fn with_builtins() -> Self { let mut env = Self::new(); // Add built-in types env.types .insert("Int".to_string(), TypeDef::Alias(Type::Int)); env.types .insert("Float".to_string(), TypeDef::Alias(Type::Float)); env.types .insert("Bool".to_string(), TypeDef::Alias(Type::Bool)); env.types .insert("String".to_string(), TypeDef::Alias(Type::String)); env.types .insert("Char".to_string(), TypeDef::Alias(Type::Char)); env.types .insert("Unit".to_string(), TypeDef::Alias(Type::Unit)); // Add Option type env.types.insert( "Option".to_string(), TypeDef::Enum(vec![ VariantDef { name: "None".to_string(), fields: VariantFieldsDef::Unit, }, VariantDef { name: "Some".to_string(), fields: VariantFieldsDef::Tuple(vec![Type::Var(0)]), }, ]), ); // Add Result type env.types.insert( "Result".to_string(), TypeDef::Enum(vec![ VariantDef { name: "Ok".to_string(), fields: VariantFieldsDef::Tuple(vec![Type::Var(0)]), }, VariantDef { name: "Err".to_string(), fields: VariantFieldsDef::Tuple(vec![Type::Var(1)]), }, ]), ); // Add Json type (represents JSON values) env.types.insert( "Json".to_string(), TypeDef::Enum(vec![ VariantDef { name: "JsonNull".to_string(), fields: VariantFieldsDef::Unit, }, VariantDef { name: "JsonBool".to_string(), fields: VariantFieldsDef::Tuple(vec![Type::Bool]), }, VariantDef { name: "JsonNumber".to_string(), fields: VariantFieldsDef::Tuple(vec![Type::Float]), }, VariantDef { name: "JsonString".to_string(), fields: VariantFieldsDef::Tuple(vec![Type::String]), }, VariantDef { name: "JsonArray".to_string(), fields: VariantFieldsDef::Tuple(vec![Type::List(Box::new(Type::Named("Json".to_string())))]), }, VariantDef { name: "JsonObject".to_string(), fields: VariantFieldsDef::Tuple(vec![Type::List(Box::new(Type::Tuple(vec![Type::String, Type::Named("Json".to_string())])))]), }, ]), ); // Add Console effect env.effects.insert( "Console".to_string(), EffectDef { name: "Console".to_string(), type_params: Vec::new(), operations: vec![ EffectOpDef { name: "print".to_string(), params: vec![("msg".to_string(), Type::String)], return_type: Type::Unit, }, EffectOpDef { name: "read".to_string(), params: Vec::new(), return_type: Type::String, }, EffectOpDef { name: "readLine".to_string(), params: Vec::new(), return_type: Type::String, }, EffectOpDef { name: "readInt".to_string(), params: Vec::new(), return_type: Type::Int, }, ], }, ); // Add Fail effect env.effects.insert( "Fail".to_string(), EffectDef { name: "Fail".to_string(), type_params: Vec::new(), operations: vec![EffectOpDef { name: "fail".to_string(), params: vec![("msg".to_string(), Type::String)], return_type: Type::Var(0), // Returns any type (never returns) }], }, ); // Add State effect env.effects.insert( "State".to_string(), EffectDef { name: "State".to_string(), type_params: vec!["S".to_string()], operations: vec![ EffectOpDef { name: "get".to_string(), params: Vec::new(), return_type: Type::Var(0), // S }, EffectOpDef { name: "put".to_string(), params: vec![("value".to_string(), Type::Var(0))], // S return_type: Type::Unit, }, ], }, ); // Add Reader effect env.effects.insert( "Reader".to_string(), EffectDef { name: "Reader".to_string(), type_params: vec!["R".to_string()], operations: vec![EffectOpDef { name: "ask".to_string(), params: Vec::new(), return_type: Type::Var(0), // R }], }, ); // Add Random effect env.effects.insert( "Random".to_string(), EffectDef { name: "Random".to_string(), type_params: Vec::new(), operations: vec![ EffectOpDef { name: "int".to_string(), params: vec![ ("min".to_string(), Type::Int), ("max".to_string(), Type::Int), ], return_type: Type::Int, }, EffectOpDef { name: "float".to_string(), params: Vec::new(), return_type: Type::Float, }, EffectOpDef { name: "bool".to_string(), params: Vec::new(), return_type: Type::Bool, }, ], }, ); // Add Time effect env.effects.insert( "Time".to_string(), EffectDef { name: "Time".to_string(), type_params: Vec::new(), operations: vec![ EffectOpDef { name: "now".to_string(), params: Vec::new(), return_type: Type::Int, // Unix timestamp in milliseconds }, EffectOpDef { name: "sleep".to_string(), params: vec![("ms".to_string(), Type::Int)], return_type: Type::Unit, }, ], }, ); // Add File effect env.effects.insert( "File".to_string(), EffectDef { name: "File".to_string(), type_params: Vec::new(), operations: vec![ EffectOpDef { name: "read".to_string(), params: vec![("path".to_string(), Type::String)], return_type: Type::String, }, EffectOpDef { name: "write".to_string(), params: vec![ ("path".to_string(), Type::String), ("content".to_string(), Type::String), ], return_type: Type::Unit, }, EffectOpDef { name: "append".to_string(), params: vec![ ("path".to_string(), Type::String), ("content".to_string(), Type::String), ], return_type: Type::Unit, }, EffectOpDef { name: "exists".to_string(), params: vec![("path".to_string(), Type::String)], return_type: Type::Bool, }, EffectOpDef { name: "delete".to_string(), params: vec![("path".to_string(), Type::String)], return_type: Type::Unit, }, EffectOpDef { name: "readDir".to_string(), params: vec![("path".to_string(), Type::String)], return_type: Type::List(Box::new(Type::String)), }, EffectOpDef { name: "isDir".to_string(), params: vec![("path".to_string(), Type::String)], return_type: Type::Bool, }, EffectOpDef { name: "mkdir".to_string(), params: vec![("path".to_string(), Type::String)], return_type: Type::Unit, }, EffectOpDef { name: "copy".to_string(), params: vec![ ("source".to_string(), Type::String), ("dest".to_string(), Type::String), ], return_type: Type::Unit, }, EffectOpDef { name: "glob".to_string(), params: vec![("pattern".to_string(), Type::String)], return_type: Type::List(Box::new(Type::String)), }, EffectOpDef { name: "tryRead".to_string(), params: vec![("path".to_string(), Type::String)], return_type: Type::App { constructor: Box::new(Type::Named("Result".to_string())), args: vec![Type::String, Type::String], }, }, EffectOpDef { name: "tryWrite".to_string(), params: vec![ ("path".to_string(), Type::String), ("content".to_string(), Type::String), ], return_type: Type::App { constructor: Box::new(Type::Named("Result".to_string())), args: vec![Type::Unit, Type::String], }, }, EffectOpDef { name: "tryDelete".to_string(), params: vec![("path".to_string(), Type::String)], return_type: Type::App { constructor: Box::new(Type::Named("Result".to_string())), args: vec![Type::Unit, Type::String], }, }, ], }, ); // Add Process effect env.effects.insert( "Process".to_string(), EffectDef { name: "Process".to_string(), type_params: Vec::new(), operations: vec![ EffectOpDef { name: "exec".to_string(), params: vec![("cmd".to_string(), Type::String)], return_type: Type::String, }, EffectOpDef { name: "execStatus".to_string(), params: vec![("cmd".to_string(), Type::String)], return_type: Type::Int, }, EffectOpDef { name: "env".to_string(), params: vec![("name".to_string(), Type::String)], return_type: Type::Option(Box::new(Type::String)), }, EffectOpDef { name: "args".to_string(), params: Vec::new(), return_type: Type::List(Box::new(Type::String)), }, EffectOpDef { name: "exit".to_string(), params: vec![("code".to_string(), Type::Int)], return_type: Type::Unit, }, EffectOpDef { name: "cwd".to_string(), params: Vec::new(), return_type: Type::String, }, EffectOpDef { name: "setCwd".to_string(), params: vec![("path".to_string(), Type::String)], return_type: Type::Unit, }, ], }, ); // Add Http effect let http_response_type = Type::Record(vec![ ("status".to_string(), Type::Int), ("body".to_string(), Type::String), ("headers".to_string(), Type::List(Box::new(Type::Tuple(vec![Type::String, Type::String])))), ]); env.effects.insert( "Http".to_string(), EffectDef { name: "Http".to_string(), type_params: Vec::new(), operations: vec![ EffectOpDef { name: "get".to_string(), params: vec![("url".to_string(), Type::String)], return_type: Type::App { constructor: Box::new(Type::Named("Result".to_string())), args: vec![http_response_type.clone(), Type::String], }, }, EffectOpDef { name: "post".to_string(), params: vec![ ("url".to_string(), Type::String), ("body".to_string(), Type::String), ], return_type: Type::App { constructor: Box::new(Type::Named("Result".to_string())), args: vec![http_response_type.clone(), Type::String], }, }, EffectOpDef { name: "postJson".to_string(), params: vec![ ("url".to_string(), Type::String), ("json".to_string(), Type::Named("Json".to_string())), ], return_type: Type::App { constructor: Box::new(Type::Named("Result".to_string())), args: vec![http_response_type.clone(), Type::String], }, }, EffectOpDef { name: "put".to_string(), params: vec![ ("url".to_string(), Type::String), ("body".to_string(), Type::String), ], return_type: Type::App { constructor: Box::new(Type::Named("Result".to_string())), args: vec![http_response_type.clone(), Type::String], }, }, EffectOpDef { name: "delete".to_string(), params: vec![("url".to_string(), Type::String)], return_type: Type::App { constructor: Box::new(Type::Named("Result".to_string())), args: vec![http_response_type.clone(), Type::String], }, }, EffectOpDef { name: "setHeader".to_string(), params: vec![ ("name".to_string(), Type::String), ("value".to_string(), Type::String), ], return_type: Type::Unit, }, EffectOpDef { name: "setTimeout".to_string(), params: vec![("seconds".to_string(), Type::Int)], return_type: Type::Unit, }, ], }, ); // Add HttpServer effect let http_request_type = Type::Record(vec![ ("method".to_string(), Type::String), ("path".to_string(), Type::String), ("body".to_string(), Type::String), ("headers".to_string(), Type::List(Box::new(Type::Tuple(vec![Type::String, Type::String])))), ]); env.effects.insert( "HttpServer".to_string(), EffectDef { name: "HttpServer".to_string(), type_params: Vec::new(), operations: vec![ EffectOpDef { name: "listen".to_string(), params: vec![("port".to_string(), Type::Int)], return_type: Type::Unit, }, EffectOpDef { name: "accept".to_string(), params: vec![], return_type: http_request_type.clone(), }, EffectOpDef { name: "respond".to_string(), params: vec![ ("status".to_string(), Type::Int), ("body".to_string(), Type::String), ], return_type: Type::Unit, }, EffectOpDef { name: "respondWithHeaders".to_string(), params: vec![ ("status".to_string(), Type::Int), ("body".to_string(), Type::String), ("headers".to_string(), Type::List(Box::new(Type::Tuple(vec![Type::String, Type::String])))), ], return_type: Type::Unit, }, EffectOpDef { name: "stop".to_string(), params: vec![], return_type: Type::Unit, }, ], }, ); // Add Test effect for test framework env.effects.insert( "Test".to_string(), EffectDef { name: "Test".to_string(), type_params: Vec::new(), operations: vec![ EffectOpDef { name: "assert".to_string(), params: vec![ ("condition".to_string(), Type::Bool), ("message".to_string(), Type::String), ], return_type: Type::Unit, }, EffectOpDef { name: "assertEqual".to_string(), params: vec![ ("expected".to_string(), Type::Var(0)), ("actual".to_string(), Type::Var(0)), ], return_type: Type::Unit, }, EffectOpDef { name: "assertEqualMsg".to_string(), params: vec![ ("expected".to_string(), Type::Var(0)), ("actual".to_string(), Type::Var(0)), ("label".to_string(), Type::String), ], return_type: Type::Unit, }, EffectOpDef { name: "assertNotEqual".to_string(), params: vec![ ("a".to_string(), Type::Var(0)), ("b".to_string(), Type::Var(0)), ], return_type: Type::Unit, }, EffectOpDef { name: "assertTrue".to_string(), params: vec![("condition".to_string(), Type::Bool)], return_type: Type::Unit, }, EffectOpDef { name: "assertFalse".to_string(), params: vec![("condition".to_string(), Type::Bool)], return_type: Type::Unit, }, EffectOpDef { name: "fail".to_string(), params: vec![("message".to_string(), Type::String)], return_type: Type::Unit, }, ], }, ); // Add Concurrent effect for concurrent/parallel execution // Task is represented as Int (task ID) env.effects.insert( "Concurrent".to_string(), EffectDef { name: "Concurrent".to_string(), type_params: Vec::new(), operations: vec![ // Spawn a new concurrent task that returns a value // Returns a Task (represented as Int task ID) EffectOpDef { name: "spawn".to_string(), params: vec![("thunk".to_string(), Type::Function { params: Vec::new(), return_type: Box::new(Type::Var(0)), effects: EffectSet::empty(), properties: PropertySet::empty(), })], return_type: Type::Int, // Task ID }, // Wait for a task to complete and get its result EffectOpDef { name: "await".to_string(), params: vec![("task".to_string(), Type::Int)], return_type: Type::Var(0), }, // Yield control to allow other tasks to run EffectOpDef { name: "yield".to_string(), params: Vec::new(), return_type: Type::Unit, }, // Sleep for milliseconds (non-blocking to other tasks) EffectOpDef { name: "sleep".to_string(), params: vec![("ms".to_string(), Type::Int)], return_type: Type::Unit, }, // Cancel a running task EffectOpDef { name: "cancel".to_string(), params: vec![("task".to_string(), Type::Int)], return_type: Type::Bool, }, // Check if a task is still running EffectOpDef { name: "isRunning".to_string(), params: vec![("task".to_string(), Type::Int)], return_type: Type::Bool, }, // Get the number of active tasks EffectOpDef { name: "taskCount".to_string(), params: Vec::new(), return_type: Type::Int, }, ], }, ); // Add Channel effect for concurrent communication env.effects.insert( "Channel".to_string(), EffectDef { name: "Channel".to_string(), type_params: Vec::new(), operations: vec![ // Create a new channel, returns channel ID EffectOpDef { name: "create".to_string(), params: Vec::new(), return_type: Type::Int, // Channel ID }, // Send a value on a channel EffectOpDef { name: "send".to_string(), params: vec![ ("channel".to_string(), Type::Int), ("value".to_string(), Type::Var(0)), ], return_type: Type::Unit, }, // Receive a value from a channel (blocks until available) EffectOpDef { name: "receive".to_string(), params: vec![("channel".to_string(), Type::Int)], return_type: Type::Var(0), }, // Try to receive (non-blocking, returns Option) EffectOpDef { name: "tryReceive".to_string(), params: vec![("channel".to_string(), Type::Int)], return_type: Type::Option(Box::new(Type::Var(0))), }, // Close a channel EffectOpDef { name: "close".to_string(), params: vec![("channel".to_string(), Type::Int)], return_type: Type::Unit, }, ], }, ); // Add Sql effect for database access // Connection is represented as Int (connection ID) let row_type = Type::Record(vec![]); // Dynamic record type env.effects.insert( "Sql".to_string(), EffectDef { name: "Sql".to_string(), type_params: Vec::new(), operations: vec![ EffectOpDef { name: "open".to_string(), params: vec![("path".to_string(), Type::String)], return_type: Type::Int, // Connection ID }, EffectOpDef { name: "openMemory".to_string(), params: vec![], return_type: Type::Int, // Connection ID }, EffectOpDef { name: "close".to_string(), params: vec![("conn".to_string(), Type::Int)], return_type: Type::Unit, }, EffectOpDef { name: "execute".to_string(), params: vec![ ("conn".to_string(), Type::Int), ("sql".to_string(), Type::String), ], return_type: Type::Int, // Rows affected }, EffectOpDef { name: "query".to_string(), params: vec![ ("conn".to_string(), Type::Int), ("sql".to_string(), Type::String), ], return_type: Type::List(Box::new(Type::var())), // List of records }, EffectOpDef { name: "queryOne".to_string(), params: vec![ ("conn".to_string(), Type::Int), ("sql".to_string(), Type::String), ], return_type: Type::Option(Box::new(Type::var())), // Optional record }, EffectOpDef { name: "beginTx".to_string(), params: vec![("conn".to_string(), Type::Int)], return_type: Type::Unit, }, EffectOpDef { name: "commit".to_string(), params: vec![("conn".to_string(), Type::Int)], return_type: Type::Unit, }, EffectOpDef { name: "rollback".to_string(), params: vec![("conn".to_string(), Type::Int)], return_type: Type::Unit, }, ], }, ); // Add Some and Ok, Err constructors // Some : fn(a) -> Option let a = Type::var(); env.bind( "Some", TypeScheme::mono(Type::function(vec![a.clone()], Type::Option(Box::new(a)))), ); // None : Option env.bind( "None", TypeScheme::mono(Type::Option(Box::new(Type::var()))), ); // Ok : fn(a) -> Result let a = Type::var(); let e = Type::var(); env.bind( "Ok", TypeScheme::mono(Type::function( vec![a.clone()], Type::App { constructor: Box::new(Type::Named("Result".to_string())), args: vec![a, e], }, )), ); // Err : fn(e) -> Result let a = Type::var(); let e = Type::var(); env.bind( "Err", TypeScheme::mono(Type::function( vec![e.clone()], Type::App { constructor: Box::new(Type::Named("Result".to_string())), args: vec![a, e], }, )), ); // Add stdlib modules as record types // List module let list_module_type = Type::Record(vec![ ( "map".to_string(), Type::function( vec![ Type::List(Box::new(Type::var())), Type::function(vec![Type::var()], Type::var()), ], Type::List(Box::new(Type::var())), ), ), ( "filter".to_string(), Type::function( vec![ Type::List(Box::new(Type::var())), Type::function(vec![Type::var()], Type::Bool), ], Type::List(Box::new(Type::var())), ), ), ( "fold".to_string(), Type::function( vec![ Type::List(Box::new(Type::var())), Type::var(), Type::function(vec![Type::var(), Type::var()], Type::var()), ], Type::var(), ), ), ( "head".to_string(), Type::function( vec![Type::List(Box::new(Type::var()))], Type::Option(Box::new(Type::var())), ), ), ( "tail".to_string(), Type::function( vec![Type::List(Box::new(Type::var()))], Type::Option(Box::new(Type::List(Box::new(Type::var())))), ), ), ( "concat".to_string(), Type::function( vec![ Type::List(Box::new(Type::var())), Type::List(Box::new(Type::var())), ], Type::List(Box::new(Type::var())), ), ), ( "reverse".to_string(), Type::function( vec![Type::List(Box::new(Type::var()))], Type::List(Box::new(Type::var())), ), ), ( "length".to_string(), Type::function(vec![Type::List(Box::new(Type::var()))], Type::Int), ), ( "get".to_string(), Type::function( vec![Type::List(Box::new(Type::var())), Type::Int], Type::Option(Box::new(Type::var())), ), ), ( "range".to_string(), Type::function(vec![Type::Int, Type::Int], Type::List(Box::new(Type::Int))), ), ( "isEmpty".to_string(), Type::function(vec![Type::List(Box::new(Type::var()))], Type::Bool), ), ( "find".to_string(), Type::function( vec![ Type::List(Box::new(Type::var())), Type::function(vec![Type::var()], Type::Bool), ], Type::Option(Box::new(Type::var())), ), ), ( "any".to_string(), Type::function( vec![ Type::List(Box::new(Type::var())), Type::function(vec![Type::var()], Type::Bool), ], Type::Bool, ), ), ( "all".to_string(), Type::function( vec![ Type::List(Box::new(Type::var())), Type::function(vec![Type::var()], Type::Bool), ], Type::Bool, ), ), ( "take".to_string(), Type::function( vec![Type::List(Box::new(Type::var())), Type::Int], Type::List(Box::new(Type::var())), ), ), ( "drop".to_string(), Type::function( vec![Type::List(Box::new(Type::var())), Type::Int], Type::List(Box::new(Type::var())), ), ), ( "forEach".to_string(), Type::function( vec![ Type::List(Box::new(Type::var())), Type::function(vec![Type::var()], Type::Unit), ], Type::Unit, ), ), ( "sort".to_string(), Type::function( vec![Type::List(Box::new(Type::var()))], Type::List(Box::new(Type::var())), ), ), ( "sortBy".to_string(), { let elem = Type::var(); Type::function( vec![ Type::List(Box::new(elem.clone())), Type::function(vec![elem.clone(), elem], Type::Int), ], Type::List(Box::new(Type::var())), ) }, ), ]); env.bind("List", TypeScheme::mono(list_module_type)); // String module let string_module_type = Type::Record(vec![ ( "split".to_string(), Type::function( vec![Type::String, Type::String], Type::List(Box::new(Type::String)), ), ), ( "join".to_string(), Type::function( vec![Type::List(Box::new(Type::String)), Type::String], Type::String, ), ), ( "trim".to_string(), Type::function(vec![Type::String], Type::String), ), ( "contains".to_string(), Type::function(vec![Type::String, Type::String], Type::Bool), ), ( "replace".to_string(), Type::function(vec![Type::String, Type::String, Type::String], Type::String), ), ( "length".to_string(), Type::function(vec![Type::String], Type::Int), ), ( "chars".to_string(), Type::function(vec![Type::String], Type::List(Box::new(Type::Char))), ), ( "lines".to_string(), Type::function(vec![Type::String], Type::List(Box::new(Type::String))), ), ( "startsWith".to_string(), Type::function(vec![Type::String, Type::String], Type::Bool), ), ( "endsWith".to_string(), Type::function(vec![Type::String, Type::String], Type::Bool), ), ( "toUpper".to_string(), Type::function(vec![Type::String], Type::String), ), ( "toLower".to_string(), Type::function(vec![Type::String], Type::String), ), ( "substring".to_string(), Type::function(vec![Type::String, Type::Int, Type::Int], Type::String), ), ( "fromChar".to_string(), Type::function(vec![Type::Char], Type::String), ), ( "parseInt".to_string(), Type::function(vec![Type::String], Type::Option(Box::new(Type::Int))), ), ( "parseFloat".to_string(), Type::function(vec![Type::String], Type::Option(Box::new(Type::Float))), ), ( "indexOf".to_string(), Type::function(vec![Type::String, Type::String], Type::Option(Box::new(Type::Int))), ), ( "lastIndexOf".to_string(), Type::function(vec![Type::String, Type::String], Type::Option(Box::new(Type::Int))), ), ]); env.bind("String", TypeScheme::mono(string_module_type)); // Json module let json_type = Type::Named("Json".to_string()); let json_module_type = Type::Record(vec![ ( "parse".to_string(), Type::function( vec![Type::String], Type::App { constructor: Box::new(Type::Named("Result".to_string())), args: vec![json_type.clone(), Type::String], }, ), ), ( "stringify".to_string(), Type::function(vec![json_type.clone()], Type::String), ), ( "prettyPrint".to_string(), Type::function(vec![json_type.clone()], Type::String), ), ( "get".to_string(), Type::function( vec![json_type.clone(), Type::String], Type::Option(Box::new(json_type.clone())), ), ), ( "getIndex".to_string(), Type::function( vec![json_type.clone(), Type::Int], Type::Option(Box::new(json_type.clone())), ), ), ( "asString".to_string(), Type::function( vec![json_type.clone()], Type::Option(Box::new(Type::String)), ), ), ( "asNumber".to_string(), Type::function( vec![json_type.clone()], Type::Option(Box::new(Type::Float)), ), ), ( "asInt".to_string(), Type::function( vec![json_type.clone()], Type::Option(Box::new(Type::Int)), ), ), ( "asBool".to_string(), Type::function( vec![json_type.clone()], Type::Option(Box::new(Type::Bool)), ), ), ( "asArray".to_string(), Type::function( vec![json_type.clone()], Type::Option(Box::new(Type::List(Box::new(json_type.clone())))), ), ), ( "isNull".to_string(), Type::function(vec![json_type.clone()], Type::Bool), ), ( "keys".to_string(), Type::function( vec![json_type.clone()], Type::List(Box::new(Type::String)), ), ), // Constructors for building JSON ( "null".to_string(), Type::function(vec![], json_type.clone()), ), ( "bool".to_string(), Type::function(vec![Type::Bool], json_type.clone()), ), ( "number".to_string(), Type::function(vec![Type::Float], json_type.clone()), ), ( "int".to_string(), Type::function(vec![Type::Int], json_type.clone()), ), ( "string".to_string(), Type::function(vec![Type::String], json_type.clone()), ), ( "array".to_string(), Type::function(vec![Type::List(Box::new(json_type.clone()))], json_type.clone()), ), ( "object".to_string(), Type::function( vec![Type::List(Box::new(Type::Tuple(vec![Type::String, json_type.clone()])))], json_type.clone(), ), ), ]); env.bind("Json", TypeScheme::mono(json_module_type)); // Option module let option_module_type = Type::Record(vec![ ( "map".to_string(), Type::function( vec![ Type::Option(Box::new(Type::var())), Type::function(vec![Type::var()], Type::var()), ], Type::Option(Box::new(Type::var())), ), ), ( "flatMap".to_string(), Type::function( vec![ Type::Option(Box::new(Type::var())), Type::function(vec![Type::var()], Type::Option(Box::new(Type::var()))), ], Type::Option(Box::new(Type::var())), ), ), ( "getOrElse".to_string(), Type::function( vec![Type::Option(Box::new(Type::var())), Type::var()], Type::var(), ), ), ( "isSome".to_string(), Type::function(vec![Type::Option(Box::new(Type::var()))], Type::Bool), ), ( "isNone".to_string(), Type::function(vec![Type::Option(Box::new(Type::var()))], Type::Bool), ), ]); env.bind("Option", TypeScheme::mono(option_module_type)); // Map module let map_v = || Type::var(); let map_type = || Type::Map(Box::new(Type::String), Box::new(Type::var())); let map_module_type = Type::Record(vec![ ( "new".to_string(), Type::function(vec![], map_type()), ), ( "set".to_string(), Type::function( vec![map_type(), Type::String, map_v()], map_type(), ), ), ( "get".to_string(), Type::function( vec![map_type(), Type::String], Type::Option(Box::new(map_v())), ), ), ( "contains".to_string(), Type::function(vec![map_type(), Type::String], Type::Bool), ), ( "remove".to_string(), Type::function(vec![map_type(), Type::String], map_type()), ), ( "keys".to_string(), Type::function(vec![map_type()], Type::List(Box::new(Type::String))), ), ( "values".to_string(), Type::function(vec![map_type()], Type::List(Box::new(map_v()))), ), ( "size".to_string(), Type::function(vec![map_type()], Type::Int), ), ( "isEmpty".to_string(), Type::function(vec![map_type()], Type::Bool), ), ( "fromList".to_string(), Type::function( vec![Type::List(Box::new(Type::Tuple(vec![Type::String, map_v()])))], map_type(), ), ), ( "toList".to_string(), Type::function( vec![map_type()], Type::List(Box::new(Type::Tuple(vec![Type::String, map_v()]))), ), ), ( "merge".to_string(), Type::function(vec![map_type(), map_type()], map_type()), ), ]); env.bind("Map", TypeScheme::mono(map_module_type)); // Result module let result_type = Type::App { constructor: Box::new(Type::Named("Result".to_string())), args: vec![Type::var(), Type::var()], }; let result_module_type = Type::Record(vec![ ( "map".to_string(), Type::function( vec![ result_type.clone(), Type::function(vec![Type::var()], Type::var()), ], result_type.clone(), ), ), ( "flatMap".to_string(), Type::function( vec![ result_type.clone(), Type::function(vec![Type::var()], result_type.clone()), ], result_type.clone(), ), ), ( "getOrElse".to_string(), Type::function(vec![result_type.clone(), Type::var()], Type::var()), ), ( "isOk".to_string(), Type::function(vec![result_type.clone()], Type::Bool), ), ( "isErr".to_string(), Type::function(vec![result_type], Type::Bool), ), ]); env.bind("Result", TypeScheme::mono(result_module_type)); // Utility functions env.bind( "print", TypeScheme::mono(Type::function(vec![Type::var()], Type::Unit)), ); env.bind( "toString", TypeScheme::mono(Type::function(vec![Type::var()], Type::String)), ); env.bind( "typeOf", TypeScheme::mono(Type::function(vec![Type::var()], Type::String)), ); // Schema module for versioned types let schema_module_type = Type::Record(vec![ ( "versioned".to_string(), Type::function( vec![Type::String, Type::Int, Type::var()], Type::var(), // Returns Versioned (treated as Any for now) ), ), ( "migrate".to_string(), Type::function( vec![Type::var(), Type::Int], Type::var(), // Returns Versioned ), ), ( "getVersion".to_string(), Type::function(vec![Type::var()], Type::Int), ), ]); env.bind("Schema", TypeScheme::mono(schema_module_type)); // Math module let math_module_type = Type::Record(vec![ ( "abs".to_string(), Type::function(vec![Type::var()], Type::var()), // Works on Int or Float ), ( "min".to_string(), Type::function(vec![Type::var(), Type::var()], Type::var()), ), ( "max".to_string(), Type::function(vec![Type::var(), Type::var()], Type::var()), ), ( "sqrt".to_string(), Type::function(vec![Type::var()], Type::Float), ), ( "pow".to_string(), Type::function(vec![Type::var(), Type::var()], Type::var()), ), ( "floor".to_string(), Type::function(vec![Type::var()], Type::Int), ), ( "ceil".to_string(), Type::function(vec![Type::var()], Type::Int), ), ( "round".to_string(), Type::function(vec![Type::var()], Type::Int), ), ( "sin".to_string(), Type::function(vec![Type::Float], Type::Float), ), ( "cos".to_string(), Type::function(vec![Type::Float], Type::Float), ), ( "atan2".to_string(), Type::function(vec![Type::Float, Type::Float], Type::Float), ), ]); env.bind("Math", TypeScheme::mono(math_module_type)); // Int module let int_module_type = Type::Record(vec![ ( "toString".to_string(), Type::function(vec![Type::Int], Type::String), ), ( "toFloat".to_string(), Type::function(vec![Type::Int], Type::Float), ), ]); env.bind("Int", TypeScheme::mono(int_module_type)); // Float module let float_module_type = Type::Record(vec![ ( "toString".to_string(), Type::function(vec![Type::Float], Type::String), ), ( "toInt".to_string(), Type::function(vec![Type::Float], Type::Int), ), ]); env.bind("Float", TypeScheme::mono(float_module_type)); env } pub fn lookup(&self, name: &str) -> Option<&TypeScheme> { self.bindings.get(name) } pub fn bind(&mut self, name: impl Into, scheme: TypeScheme) { self.bindings.insert(name.into(), scheme); } pub fn extend(&self, name: impl Into, scheme: TypeScheme) -> TypeEnv { let mut env = self.clone(); env.bind(name, scheme); env } pub fn lookup_effect(&self, name: &str) -> Option<&EffectDef> { self.effects.get(name) } pub fn lookup_effect_op(&self, effect: &str, op: &str) -> Option<&EffectOpDef> { self.effects .get(effect)? .operations .iter() .find(|o| o.name == op) } /// Generalize a type to a type scheme pub fn generalize(&self, typ: &Type) -> TypeScheme { let env_vars: HashSet = self.bindings.values().flat_map(|s| s.free_vars()).collect(); let type_vars: Vec = typ .free_vars() .into_iter() .filter(|v| !env_vars.contains(v)) .collect(); TypeScheme::poly(type_vars, typ.clone()) } /// Expand a Named type to its underlying structural type if it's an alias /// This is needed for unifying record type aliases with record literals pub fn expand_type_alias(&self, ty: &Type) -> Type { match ty { Type::Named(name) => { if let Some(type_def) = self.types.get(name) { match type_def { TypeDef::Alias(inner) => self.expand_type_alias(inner), // For enums and records, keep the Named type _ => ty.clone(), } } else { ty.clone() } } Type::Function { params, return_type, effects, properties } => { Type::Function { params: params.iter().map(|p| self.expand_type_alias(p)).collect(), return_type: Box::new(self.expand_type_alias(return_type)), effects: effects.clone(), properties: properties.clone(), } } Type::App { constructor, args } => { Type::App { constructor: Box::new(self.expand_type_alias(constructor)), args: args.iter().map(|a| self.expand_type_alias(a)).collect(), } } Type::Tuple(elems) => { Type::Tuple(elems.iter().map(|e| self.expand_type_alias(e)).collect()) } Type::Record(fields) => { Type::Record(fields.iter().map(|(n, t)| (n.clone(), self.expand_type_alias(t))).collect()) } Type::List(inner) => { Type::List(Box::new(self.expand_type_alias(inner))) } Type::Option(inner) => { Type::Option(Box::new(self.expand_type_alias(inner))) } Type::Map(k, v) => { Type::Map(Box::new(self.expand_type_alias(k)), Box::new(self.expand_type_alias(v))) } Type::Versioned { base, version } => { Type::Versioned { base: Box::new(self.expand_type_alias(base)), version: version.clone(), } } // Primitives and type variables stay as-is _ => ty.clone(), } } } /// Unify types with type alias expansion pub fn unify_with_env(t1: &Type, t2: &Type, env: &TypeEnv) -> Result { let expanded1 = env.expand_type_alias(t1); let expanded2 = env.expand_type_alias(t2); unify(&expanded1, &expanded2) } /// Unification of types pub fn unify(t1: &Type, t2: &Type) -> Result { match (t1, t2) { // Same type (Type::Int, Type::Int) | (Type::Float, Type::Float) | (Type::Bool, Type::Bool) | (Type::String, Type::String) | (Type::Char, Type::Char) | (Type::Unit, Type::Unit) | (Type::Error, _) | (_, Type::Error) => Ok(Substitution::new()), // Named types (Type::Named(a), Type::Named(b)) if a == b => Ok(Substitution::new()), // Type variables (Type::Var(v), t) | (t, Type::Var(v)) => { if let Type::Var(v2) = t { if v == v2 { return Ok(Substitution::new()); } } if t.contains_var(*v) { return Err(format!("Occurs check failed: ?{} occurs in {}", v, t)); } let mut subst = Substitution::new(); subst.insert(*v, t.clone()); Ok(subst) } // Function types ( Type::Function { params: p1, return_type: r1, effects: e1, properties: props1, }, Type::Function { params: p2, return_type: r2, effects: e2, properties: props2, }, ) => { if p1.len() != p2.len() { return Err(format!( "Function arity mismatch: expected {} params, got {}", p1.len(), p2.len() )); } // Function's required effects (e1) must be a subset of available effects (e2) // A pure function (empty effects) can be called anywhere // A function requiring {Logger} can be called in context with {Logger} or {Logger, Console} // When expected effects (e2) are empty, it means "no constraint" (e.g., callback parameter) // so we allow any actual effects through if !e2.is_empty() && !e1.is_subset(&e2) { return Err(format!( "Effect mismatch: expected {{{}}}, got {{{}}}", e1, e2 )); } // Properties are not checked during unification - they are guarantees, not constraints // A pure function can be used anywhere a function is expected // Property requirements are enforced via where clauses, not type unification let _ = (props1, props2); // Acknowledge but don't enforce let mut subst = Substitution::new(); for (a, b) in p1.iter().zip(p2.iter()) { let s = unify(&a.apply(&subst), &b.apply(&subst))?; subst = subst.compose(&s); } let s = unify(&r1.apply(&subst), &r2.apply(&subst))?; Ok(subst.compose(&s)) } // Type applications ( Type::App { constructor: c1, args: a1, }, Type::App { constructor: c2, args: a2, }, ) => { if a1.len() != a2.len() { return Err(format!( "Type argument count mismatch: {} vs {}", a1.len(), a2.len() )); } let mut subst = unify(c1, c2)?; for (a, b) in a1.iter().zip(a2.iter()) { let s = unify(&a.apply(&subst), &b.apply(&subst))?; subst = subst.compose(&s); } Ok(subst) } // Tuples (Type::Tuple(a), Type::Tuple(b)) => { if a.len() != b.len() { return Err(format!("Tuple size mismatch: {} vs {}", a.len(), b.len())); } let mut subst = Substitution::new(); for (x, y) in a.iter().zip(b.iter()) { let s = unify(&x.apply(&subst), &y.apply(&subst))?; subst = subst.compose(&s); } Ok(subst) } // Records (structural) (Type::Record(a), Type::Record(b)) => { if a.len() != b.len() { return Err("Record field count mismatch".to_string()); } let mut subst = Substitution::new(); for ((n1, t1), (n2, t2)) in a.iter().zip(b.iter()) { if n1 != n2 { return Err(format!("Record field name mismatch: {} vs {}", n1, n2)); } let s = unify(&t1.apply(&subst), &t2.apply(&subst))?; subst = subst.compose(&s); } Ok(subst) } // List (Type::List(a), Type::List(b)) => unify(a, b), // Option (Type::Option(a), Type::Option(b)) => unify(a, b), // Map (Type::Map(k1, v1), Type::Map(k2, v2)) => { let s1 = unify(k1, k2)?; let s2 = unify(&v1.apply(&s1), &v2.apply(&s1))?; Ok(s1.compose(&s2)) } // Versioned types ( Type::Versioned { base: b1, version: v1, }, Type::Versioned { base: b2, version: v2, }, ) => { // Check version compatibility if !versions_compatible(v1, v2) { return Err(format!( "Version mismatch: {} is not compatible with {}", v1, v2 )); } unify(b1, b2) } // Versioned type with non-versioned: treat as the base type (Type::Versioned { base, .. }, other) | (other, Type::Versioned { base, .. }) => { unify(base, other) } _ => Err(format!("Cannot unify {} with {}", t1, t2)), } } /// Check if two version constraints are compatible fn versions_compatible(v1: &VersionInfo, v2: &VersionInfo) -> bool { match (v1, v2) { // Same exact version (VersionInfo::Exact(a), VersionInfo::Exact(b)) => a == b, // AtLeast is compatible if the other version is >= the minimum (VersionInfo::AtLeast(min), VersionInfo::Exact(v)) | (VersionInfo::Exact(v), VersionInfo::AtLeast(min)) => v >= min, // Two AtLeast constraints: use the higher minimum (VersionInfo::AtLeast(_), VersionInfo::AtLeast(_)) => true, // compatible, use max // Latest is compatible with anything (VersionInfo::Latest, _) | (_, VersionInfo::Latest) => true, } }