diff --git a/src/ast/compiler/type_checker.rs b/src/ast/compiler/type_checker.rs index 4cd2011..9d1d8ef 100644 --- a/src/ast/compiler/type_checker.rs +++ b/src/ast/compiler/type_checker.rs @@ -225,3 +225,72 @@ impl TypeChecker { }) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::parser::Parser; + use crate::ast::compiler::binder::Binder; + use crate::ast::types::StaticType; + use std::cell::RefCell; + + fn check_source(source: &str) -> TypedNode { + let mut parser = Parser::new(source).unwrap(); + let untyped = parser.parse_expression().unwrap(); + let globals = Rc::new(RefCell::new(HashMap::new())); + let bound = Binder::bind_root(globals, &untyped).unwrap(); + let global_types = Rc::new(RefCell::new(HashMap::new())); + let checker = TypeChecker::new(global_types); + checker.check(bound).unwrap() + } + + #[test] + fn test_inference_constants() { + assert_eq!(check_source("10").ty, StaticType::Int); + assert_eq!(check_source("10.5").ty, StaticType::Float); + assert_eq!(check_source("true").ty, StaticType::Bool); + assert_eq!(check_source("\"hello\"").ty, StaticType::Text); + } + + #[test] + fn test_inference_variable_propagation() { + // (do (def x 10) x) -> The last 'x' must be Int + let typed = check_source("(do (def x 10) x)"); + if let BoundKind::Block { exprs } = typed.kind { + let last_expr = exprs.last().unwrap(); + assert_eq!(last_expr.ty, StaticType::Int, "Variable 'x' should be inferred as Int"); + } else { panic!("Expected block"); } + } + + #[test] + fn test_inference_block_type() { + // Block type = last expression type + assert_eq!(check_source("(do 1 2.5)").ty, StaticType::Float); + assert_eq!(check_source("(do 1.5 \"test\")").ty, StaticType::Text); + } + + #[test] + fn test_inference_lambda_return() { + // (fn [a] 10) -> fn(any) -> Int + let typed = check_source("(fn [a] 10)"); + if let StaticType::Function { ret, .. } = typed.ty { + assert_eq!(*ret, StaticType::Int); + } else { panic!("Expected function type, got {:?}", typed.ty); } + + // Nested: (fn [] (do 1 2.5)) -> fn() -> Float + let typed_nested = check_source("(fn [] (do 1 2.5))"); + if let StaticType::Function { ret, .. } = typed_nested.ty { + assert_eq!(*ret, StaticType::Float); + } else { panic!("Expected function type"); } + } + + #[test] + fn test_inference_assignment_updates_type() { + // (do (def x 10) (assign x 20.5) x) -> x becomes Float after assignment + let typed = check_source("(do (def x 10) (assign x 20.5) x)"); + if let BoundKind::Block { exprs } = typed.kind { + let last_expr = exprs.last().unwrap(); + assert_eq!(last_expr.ty, StaticType::Float, "Variable 'x' should be specialized to Float after assignment"); + } else { panic!("Expected block"); } + } +}