Add missing List operations requested by ergon game engine project: - findIndex(list, predicate) -> Option<Int> - zip(list1, list2) -> List<(A, B)> - flatten(listOfLists) -> List<A> - contains(list, element) -> Bool Resolves ergon porting blocker #4. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2402 lines
82 KiB
Rust
2402 lines
82 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>),
|
|
/// 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) => 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<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) => 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, "<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));
|
|
|
|
// 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::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),
|
|
|
|
// 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,
|
|
}
|
|
}
|