#[cfg(test)]
use crate::ast::SourceLoc;
use crate::ast::types::{Effect, StackType, Type};
use crate::ast::{Expr, MatchBranch, Pattern, Program, WordDef};
use crate::typechecker::environment::Environment;
use crate::typechecker::errors::{TypeError, TypeResult};
use crate::typechecker::unification::{unify_stack_types, unify_types};
pub struct TypeChecker {
env: Environment,
}
impl TypeChecker {
pub fn new() -> Self {
TypeChecker {
env: Environment::new(),
}
}
pub fn check_program(&mut self, program: &Program) -> TypeResult<()> {
for typedef in &program.type_defs {
self.env.add_type(typedef.clone());
}
for word_def in &program.word_defs {
self.check_word_def(word_def)?;
}
Ok(())
}
fn check_word_def(&mut self, word: &WordDef) -> TypeResult<()> {
let mut current_stack = word.effect.inputs.clone();
for expr in &word.body {
current_stack = self.check_expr(expr, current_stack)?;
}
let (_, _) = unify_stack_types(¤t_stack, &word.effect.outputs).map_err(|_| {
TypeError::EffectMismatch {
expected: word.effect.clone(),
actual: Effect::new(word.effect.inputs.clone(), current_stack),
word: word.name.clone(),
}
})?;
self.env.add_word(word.name.clone(), word.effect.clone());
Ok(())
}
fn check_expr(&self, expr: &Expr, stack: StackType) -> TypeResult<StackType> {
match expr {
Expr::IntLit(_, _) => {
Ok(stack.push(Type::Int))
}
Expr::BoolLit(_, _) => {
Ok(stack.push(Type::Bool))
}
Expr::StringLit(_, _) => {
Ok(stack.push(Type::String))
}
Expr::WordCall(name, _) => {
let effect = self
.env
.lookup_word(name)
.ok_or_else(|| TypeError::UndefinedWord { name: name.clone() })?;
self.apply_effect(effect, stack, name)
}
Expr::Quotation(_exprs, _) => {
let quotation_effect = Effect::new(StackType::empty(), StackType::empty());
Ok(stack.push(Type::Quotation(Box::new(quotation_effect))))
}
Expr::Match { branches, loc: _ } => {
self.check_match(branches, stack)
}
Expr::If {
then_branch,
else_branch,
loc: _,
} => {
let (stack_after_cond, cond_type) =
stack.pop().ok_or_else(|| TypeError::StackUnderflow {
word: "if".to_string(),
required: 1,
available: 0,
})?;
unify_types(&cond_type, &Type::Bool).map_err(|_| TypeError::TypeMismatch {
expected: Type::Bool,
actual: cond_type,
context: "if condition".to_string(),
})?;
let then_stack = self.check_expr(then_branch, stack_after_cond.clone())?;
let else_stack = self.check_expr(else_branch, stack_after_cond)?;
let (_, _) =
unify_stack_types(&then_stack, &else_stack).map_err(|_| TypeError::Other {
message: "if branches produce incompatible stack effects".to_string(),
})?;
Ok(then_stack)
}
}
}
fn apply_effect(
&self,
effect: &Effect,
stack: StackType,
word_name: &str,
) -> TypeResult<StackType> {
let input_depth = effect.inputs.depth().unwrap_or(0);
let stack_depth = stack.depth().unwrap_or(0);
if stack_depth < input_depth {
return Err(Box::new(TypeError::StackUnderflow {
word: word_name.to_string(),
required: input_depth,
available: stack_depth,
}));
}
let mut remaining_stack = stack.clone();
let mut consumed = Vec::new();
for _ in 0..input_depth {
if let Some((rest, top)) = remaining_stack.pop() {
consumed.push(top);
remaining_stack = rest;
} else {
return Err(Box::new(TypeError::StackUnderflow {
word: word_name.to_string(),
required: input_depth,
available: consumed.len(),
}));
}
}
consumed.reverse();
let consumed_stack = StackType::from_vec(consumed);
let (type_subst, _stack_subst) = unify_stack_types(&consumed_stack, &effect.inputs)
.map_err(|e| TypeError::Other {
message: format!("Cannot apply '{}': input type mismatch: {}", word_name, e),
})?;
let output_stack = Self::apply_type_substitution(&effect.outputs, &type_subst);
let mut result = remaining_stack;
let mut outputs_vec = Vec::new();
let mut temp = output_stack;
while let Some((rest, top)) = temp.pop() {
outputs_vec.push(top);
temp = rest;
}
outputs_vec.reverse();
for ty in outputs_vec {
result = result.push(ty);
}
Ok(result)
}
fn apply_type_substitution(
stack: &StackType,
subst: &crate::typechecker::unification::Substitution,
) -> StackType {
match stack {
StackType::Empty => StackType::Empty,
StackType::Cons { rest, top } => {
let new_rest = Self::apply_type_substitution(rest, subst);
let new_top = Self::apply_type_subst_to_type(top, subst);
new_rest.push(new_top)
}
StackType::RowVar(name) => {
StackType::RowVar(name.clone())
}
}
}
fn apply_type_subst_to_type(
ty: &Type,
subst: &crate::typechecker::unification::Substitution,
) -> Type {
match ty {
Type::Var(name) => subst.get(name).cloned().unwrap_or_else(|| ty.clone()),
Type::Named { name, args } => Type::Named {
name: name.clone(),
args: args
.iter()
.map(|arg| Self::apply_type_subst_to_type(arg, subst))
.collect(),
},
Type::Quotation(eff) => {
Type::Quotation(eff.clone())
}
_ => ty.clone(),
}
}
fn check_match(&self, branches: &[MatchBranch], stack: StackType) -> TypeResult<StackType> {
if branches.is_empty() {
return Err(Box::new(TypeError::Other {
message: "Empty pattern match".to_string(),
}));
}
let (stack_after_pop, scrutinee_type) =
stack.pop().ok_or_else(|| TypeError::StackUnderflow {
word: "match".to_string(),
required: 1,
available: 0,
})?;
let type_name = match &scrutinee_type {
Type::Named { name, .. } => name.clone(),
_ => {
return Err(Box::new(TypeError::Other {
message: format!("Cannot pattern match on non-ADT type: {}", scrutinee_type),
}));
}
};
let variants =
self.env
.get_variants(&type_name)
.ok_or_else(|| TypeError::UndefinedType {
name: type_name.clone(),
})?;
let covered_variants: Vec<_> = branches
.iter()
.map(|b| match &b.pattern {
Pattern::Variant { name } => name.as_str(),
})
.collect();
let missing: Vec<_> = variants
.iter()
.filter(|v| !covered_variants.contains(&v.name.as_str()))
.map(|v| v.name.clone())
.collect();
if !missing.is_empty() {
return Err(Box::new(TypeError::NonExhaustiveMatch {
type_name: type_name.clone(),
missing_variants: missing,
}));
}
let mut branch_results = Vec::new();
for branch in branches {
let variant = variants
.iter()
.find(|v| match &branch.pattern {
Pattern::Variant { name } => v.name == *name,
})
.ok_or_else(|| TypeError::Other {
message: "Unknown variant in pattern".to_string(),
})?;
let mut branch_stack = stack_after_pop.clone();
for field_type in &variant.fields {
branch_stack = branch_stack.push(field_type.clone());
}
for expr in &branch.body {
branch_stack = self.check_expr(expr, branch_stack)?;
}
branch_results.push(branch_stack);
}
let first_result = &branch_results[0];
for (i, result) in branch_results.iter().enumerate().skip(1) {
let (_, _) = unify_stack_types(first_result, result).map_err(|_| {
TypeError::InconsistentBranchEffects {
type_name: type_name.clone(),
expected: Effect::new(stack_after_pop.clone(), first_result.clone()),
actual: Effect::new(stack_after_pop.clone(), result.clone()),
branch: format!("branch {}", i),
}
})?;
}
Ok(first_result.clone())
}
}
impl Default for TypeChecker {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_check_literals() {
let checker = TypeChecker::new();
let stack = StackType::empty();
let result = checker.check_expr(&Expr::IntLit(42, SourceLoc::unknown()), stack.clone());
assert!(result.is_ok());
let stack_with_int = result.unwrap();
assert_eq!(stack_with_int.depth(), Some(1));
let result = checker.check_expr(&Expr::BoolLit(true, SourceLoc::unknown()), stack.clone());
assert!(result.is_ok());
}
#[test]
fn test_check_builtin_word() {
let checker = TypeChecker::new();
let stack = StackType::empty().push(Type::Int);
let result = checker.check_expr(
&Expr::WordCall("dup".to_string(), SourceLoc::unknown()),
stack,
);
if let Err(e) = &result {
eprintln!("Error: {:?}", e);
}
assert!(result.is_ok());
let result_stack = result.unwrap();
assert_eq!(result_stack.depth(), Some(2));
}
#[test]
fn test_undefined_word() {
let checker = TypeChecker::new();
let stack = StackType::empty();
let result = checker.check_expr(
&Expr::WordCall("unknown".to_string(), SourceLoc::unknown()),
stack,
);
assert!(result.is_err());
match *result.unwrap_err() {
TypeError::UndefinedWord { name } => assert_eq!(name, "unknown"),
_ => panic!("Expected UndefinedWord error"),
}
}
#[test]
fn test_stack_underflow() {
let checker = TypeChecker::new();
let stack = StackType::empty();
let result = checker.check_expr(
&Expr::WordCall("+".to_string(), SourceLoc::unknown()),
stack,
);
assert!(result.is_err());
match *result.unwrap_err() {
TypeError::StackUnderflow { .. } => (),
e => panic!("Expected StackUnderflow, got {:?}", e),
}
}
}