feat: Add type checking to the compiler

Introduces the `TypeChecker` struct and its associated `TypeContext` for
performing static type analysis on the abstract syntax tree.

This change includes:
- A new `type_checker.rs` module.
- Integration of `TypeChecker` into the `Environment::compile` method.
- Updates to `Environment` to manage global types.
- Renaming `bound_nodes.rs`'s `BoundNode` to `TypedNode` to reflect its
  type-checked nature.
- Refinements in binder and macro expansion to accommodate type
  information.
This commit is contained in:
Michael Schimmel
2026-02-19 12:22:39 +01:00
parent 4673ea78b9
commit d93727e198
8 changed files with 270 additions and 20 deletions
+27 -11
View File
@@ -5,7 +5,7 @@ use crate::ast::types::{Value, StaticType, Object};
use crate::ast::nodes::{Node, UntypedKind, Symbol};
use crate::ast::parser::Parser;
use crate::ast::compiler::binder::Binder;
use crate::ast::compiler::BoundKind;
use crate::ast::compiler::{TypedNode, TypeChecker};
use crate::ast::vm::{VM, TracingObserver};
use crate::ast::compiler::tco::TCO;
@@ -13,14 +13,16 @@ use crate::ast::compiler::dumper::Dumper;
use crate::ast::compiler::macros::{MacroExpander, MacroRegistry, MacroEvaluator};
pub struct Environment {
pub global_names: Rc<RefCell<HashMap<Symbol, (u32, StaticType)>>>,
pub global_names: Rc<RefCell<HashMap<Symbol, u32>>>,
pub global_types: Rc<RefCell<HashMap<u32, StaticType>>>,
pub global_values: Rc<RefCell<Vec<Value>>>,
pub debug_mode: bool,
}
/// Evaluator used during macro expansion to allow compile-time logic.
struct RuntimeMacroEvaluator {
global_names: Rc<RefCell<HashMap<Symbol, (u32, StaticType)>>>,
global_names: Rc<RefCell<HashMap<Symbol, u32>>>,
global_types: Rc<RefCell<HashMap<u32, StaticType>>>,
global_values: Rc<RefCell<Vec<Value>>>,
}
@@ -34,8 +36,12 @@ impl MacroEvaluator for RuntimeMacroEvaluator {
// 2. Full evaluation for complex compile-time expressions
let bound_ast = Binder::bind_root(self.global_names.clone(), node)?;
let checker = TypeChecker::new(self.global_types.clone());
let typed_ast = checker.check(bound_ast)?;
let mut vm = VM::new(self.global_values.clone());
vm.run(&bound_ast)
vm.run(&typed_ast)
}
}
@@ -49,6 +55,7 @@ impl Environment {
pub fn new() -> Self {
let env = Self {
global_names: Rc::new(RefCell::new(HashMap::new())),
global_types: Rc::new(RefCell::new(HashMap::new())),
global_values: Rc::new(RefCell::new(Vec::new())),
debug_mode: false,
};
@@ -63,6 +70,7 @@ impl Environment {
fn get_expander(&self) -> MacroExpander<RuntimeMacroEvaluator> {
let evaluator = RuntimeMacroEvaluator {
global_names: self.global_names.clone(),
global_types: self.global_types.clone(),
global_values: self.global_values.clone(),
};
MacroExpander::new(MacroRegistry::new(), evaluator)
@@ -70,10 +78,12 @@ impl Environment {
pub fn register_native(&self, name: &str, ty: StaticType, func: impl Fn(Vec<Value>) -> Value + 'static) {
let mut names = self.global_names.borrow_mut();
let mut types = self.global_types.borrow_mut();
let mut values = self.global_values.borrow_mut();
let idx = values.len() as u32;
names.insert(Symbol::from(name), (idx, ty));
names.insert(Symbol::from(name), idx);
types.insert(idx, ty);
values.push(Value::Function(Rc::new(func)));
}
@@ -150,8 +160,8 @@ impl Environment {
Ok(Dumper::dump(&linked))
}
/// Frontend: Parse -> Expand Macros -> Bind & Type Check
pub fn compile(&self, source: &str) -> Result<Node<BoundKind, StaticType>, String> {
/// Frontend: Parse -> Expand Macros -> Bind -> Type Check
pub fn compile(&self, source: &str) -> Result<TypedNode, String> {
// 1. Parse
let mut parser = Parser::new(source)?;
let untyped_ast = parser.parse_expression()?;
@@ -164,17 +174,23 @@ impl Environment {
// 3. Expand Macros
let expanded_ast = self.get_expander().expand(untyped_ast)?;
// 4. Bind & Type Check
Binder::bind_root(self.global_names.clone(), &expanded_ast)
// 4. Bind
let bound_ast = Binder::bind_root(self.global_names.clone(), &expanded_ast)?;
// 5. Type Check
let checker = TypeChecker::new(self.global_types.clone());
let typed_ast = checker.check(bound_ast)?;
Ok(typed_ast)
}
/// Backend: Optimization (TCO, etc.)
pub fn link(&self, node: Node<BoundKind, StaticType>) -> Node<BoundKind, StaticType> {
pub fn link(&self, node: TypedNode) -> TypedNode {
TCO::optimize(node)
}
/// Runtime: Execute the linked AST in the VM
pub fn run(&self, node: &Node<BoundKind, StaticType>) -> Result<Value, String> {
pub fn run(&self, node: &TypedNode) -> Result<Value, String> {
let mut vm = VM::new(self.global_values.clone());
vm.run(node)
}