Files
lux/src/types.rs
Brandon Lucas 694e4ec999 feat: add Ref cells for mutable state (Ref.new, Ref.get, Ref.set, Ref.update)
Implements WISH-013 mutable state primitives. Ref<T> is a mutable container
using existing module call syntax. Supported across interpreter, JS, and C backends.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 20:01:29 -05:00

2438 lines
83 KiB
Rust

//! 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<Type>,
return_type: Box<Type>,
effects: EffectSet,
properties: PropertySet,
},
/// Generic type application: List<Int>, Option<String>
App {
constructor: Box<Type>,
args: Vec<Type>,
},
/// Named type (user-defined or built-in)
Named(String),
/// Tuple type
Tuple(Vec<Type>),
/// Record type
Record(Vec<(String, Type)>),
/// List type (sugar for App(List, [T]))
List(Box<Type>),
/// Option type (sugar for App(Option, [T]))
Option(Box<Type>),
/// Map type (sugar for App(Map, [K, V]))
Map(Box<Type>, Box<Type>),
/// Ref type — mutable reference cell holding a value of type T
Ref(Box<Type>),
/// Versioned type (e.g., User @v2)
Versioned {
base: Box<Type>,
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<Type>, 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<Type>, 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<Type>,
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) | Type::Ref(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::Ref(inner) => Type::Ref(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<usize> {
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) | Type::Ref(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::Ref(inner) => write!(f, "Ref<{}>", inner),
Type::Map(k, v) => write!(f, "Map<{}, {}>", k, v),
Type::Versioned { base, version } => {
write!(f, "{} {}", base, version)
}
Type::Error => write!(f, "<error>"),
}
}
}
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<String>,
}
impl EffectSet {
pub fn empty() -> Self {
Self {
effects: HashSet::new(),
}
}
pub fn single(effect: impl Into<String>) -> Self {
let mut effects = HashSet::new();
effects.insert(effect.into());
Self { effects }
}
pub fn from_iter(iter: impl IntoIterator<Item = String>) -> 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<String>) {
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<Property>,
}
/// 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<crate::ast::BehavioralProperty> 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<usize, Type>,
}
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<usize>,
pub typ: Type,
}
impl TypeScheme {
pub fn mono(typ: Type) -> Self {
Self {
vars: Vec::new(),
typ,
}
}
pub fn poly(vars: Vec<usize>, 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<usize> {
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<String>,
pub operations: Vec<EffectOpDef>,
}
/// 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<String, TypeScheme>,
/// Type definitions
pub types: HashMap<String, TypeDef>,
/// Effect definitions
pub effects: HashMap<String, EffectDef>,
/// Handler types
pub handlers: HashMap<String, HandlerDef>,
/// Trait definitions
pub traits: HashMap<String, TraitDef>,
/// Trait implementations: (trait_name, type) -> impl
pub trait_impls: Vec<TraitImpl>,
}
/// Type definition
#[derive(Debug, Clone)]
pub enum TypeDef {
Alias(Type),
Record(Vec<(String, Type)>),
Enum(Vec<VariantDef>),
}
/// 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<Type>),
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<F>)
pub type_params: Vec<String>,
/// Super traits that must be implemented
pub super_traits: Vec<TraitBoundDef>,
/// Method signatures
pub methods: Vec<TraitMethodDef>,
}
/// A trait bound in type definitions
#[derive(Debug, Clone)]
pub struct TraitBoundDef {
pub trait_name: String,
pub type_args: Vec<Type>,
}
/// A method signature in a trait
#[derive(Debug, Clone)]
pub struct TraitMethodDef {
pub name: String,
pub type_params: Vec<String>,
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<Type>,
/// The type this impl is for
pub target_type: Type,
/// Type parameters on the impl (e.g., impl<T> Show for List<T>)
pub type_params: Vec<String>,
/// Constraints on type parameters (e.g., where T: Show)
pub constraints: Vec<(String, Vec<TraitBoundDef>)>,
/// Method implementations
pub methods: HashMap<String, Type>,
}
/// A trait constraint on a type variable
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TraitConstraintDef {
pub type_var: String,
pub bounds: Vec<TraitBoundDef>,
}
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<A> (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<a>
let a = Type::var();
env.bind(
"Some",
TypeScheme::mono(Type::function(vec![a.clone()], Type::Option(Box::new(a)))),
);
// None : Option<a>
env.bind(
"None",
TypeScheme::mono(Type::Option(Box::new(Type::var()))),
);
// Ok : fn(a) -> Result<a, e>
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<a, e>
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())),
),
),
(
"findIndex".to_string(),
Type::function(
vec![
Type::List(Box::new(Type::var())),
Type::function(vec![Type::var()], Type::Bool),
],
Type::Option(Box::new(Type::Int)),
),
),
(
"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())),
)
},
),
(
"zip".to_string(),
Type::function(
vec![
Type::List(Box::new(Type::var())),
Type::List(Box::new(Type::var())),
],
Type::List(Box::new(Type::Tuple(vec![Type::var(), Type::var()]))),
),
),
(
"flatten".to_string(),
Type::function(
vec![Type::List(Box::new(Type::List(Box::new(Type::var()))))],
Type::List(Box::new(Type::var())),
),
),
(
"contains".to_string(),
Type::function(
vec![Type::List(Box::new(Type::var())), Type::var()],
Type::Bool,
),
),
]);
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));
// Ref module
let ref_inner = || Type::var();
let ref_type = || Type::Ref(Box::new(Type::var()));
let ref_module_type = Type::Record(vec![
(
"new".to_string(),
Type::function(vec![ref_inner()], ref_type()),
),
(
"get".to_string(),
Type::function(vec![ref_type()], ref_inner()),
),
(
"set".to_string(),
Type::function(vec![ref_type(), ref_inner()], Type::Unit),
),
(
"update".to_string(),
Type::function(
vec![ref_type(), Type::function(vec![ref_inner()], ref_inner())],
Type::Unit,
),
),
]);
env.bind("Ref", TypeScheme::mono(ref_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<String>, scheme: TypeScheme) {
self.bindings.insert(name.into(), scheme);
}
pub fn extend(&self, name: impl Into<String>, 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<usize> = self.bindings.values().flat_map(|s| s.free_vars()).collect();
let type_vars: Vec<usize> = 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::Ref(inner) => {
Type::Ref(Box::new(self.expand_type_alias(inner)))
}
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<Substitution, String> {
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<Substitution, String> {
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),
// Ref
(Type::Ref(a), Type::Ref(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,
}
}