From 8082f4c2e63305b36fa05ba1bd60c34595b42ca7 Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Thu, 12 Mar 2026 15:37:37 -0500 Subject: [PATCH] Add type-checking to classes/fields/constructors. --- dmc-lib/src/ast/class.rs | 69 ++++++++++++++++++++++--- dmc-lib/src/ast/constructor.rs | 32 ++++++++++++ dmc-lib/src/ast/expression_statement.rs | 12 ++--- dmc-lib/src/ast/field.rs | 12 +++++ dmc-lib/src/ast/function.rs | 7 ++- dmc-lib/src/ast/parameter.rs | 2 +- dmc-lib/src/ast/statement.rs | 9 ++-- dmc-lib/src/ast/type_use.rs | 2 +- 8 files changed, 121 insertions(+), 24 deletions(-) diff --git a/dmc-lib/src/ast/class.rs b/dmc-lib/src/ast/class.rs index 4532969..de14b46 100644 --- a/dmc-lib/src/ast/class.rs +++ b/dmc-lib/src/ast/class.rs @@ -5,6 +5,7 @@ use crate::diagnostic::Diagnostic; use crate::source_range::SourceRange; use crate::symbol::class_symbol::ClassSymbol; use crate::symbol_table::{SymbolInsertError, SymbolTable}; +use std::collections::HashSet; use std::rc::Rc; pub struct Class { @@ -138,10 +139,6 @@ impl Class { } pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec> { - // We need to determine if fields are initialized or not (the latter is an error). - // First phase: check all fields, then check constructor, leaving pending those things that - // are fields <- initialized by constructor. Then circle back to fields and check all are - // initialized let mut diagnostics: Vec = vec![]; self.fields @@ -151,7 +148,58 @@ impl Class { .flatten() .for_each(|diagnostic| diagnostics.push(diagnostic)); - // todo check constructor and functions and re-check fields + if let Some(constructor) = &mut self.constructor { + match constructor.type_check(symbol_table) { + Ok(_) => {} + Err(mut constructor_diagnostics) => { + diagnostics.append(&mut constructor_diagnostics); + } + } + } + + self.functions + .iter_mut() + .map(|function| function.type_check(symbol_table)) + .filter_map(Result::err) + .flatten() + .for_each(|diagnostic| diagnostics.push(diagnostic)); + + // We need to determine if fields are initialized or not (the latter is an error). + // First phase: check all fields, then check constructor, leaving pending those things that + // are fields <- initialized by constructor. Then circle back to fields and check all are + // initialized + let mut initialized_field_names: HashSet<&str> = HashSet::new(); + + for field in &self.fields { + if field.initializer().is_some() { + initialized_field_names.insert(field.declared_name()); + } + } + + // check constructor + if let Some(constructor) = &self.constructor { + for statement in constructor.statements() { + match statement { + _ => { + // no-op for now, because we don't yet have assign statements + } + } + } + } + + // check that all fields are present in the hash set + for field in &self.fields { + if !initialized_field_names.contains(field.declared_name()) { + diagnostics.push( + Diagnostic::new( + &format!("Field {} is not initialized.", field.declared_name()), + field.declared_name_source_range().start(), + field.declared_name_source_range().end(), + ) + .with_reporter(file!(), line!()), + ) + } + } if diagnostics.is_empty() { Ok(()) @@ -167,11 +215,11 @@ mod tests { use crate::parser::parse_compilation_unit; #[test] - fn name_analysis_no_diagnostics() { + fn complete_example_no_diagnostics() { let parse_result = parse_compilation_unit( " class Foo - mut bar: Int + mut bar: Int = 42 ctor(_bar: Int) end @@ -210,5 +258,12 @@ mod tests { panic!("{:?}", diagnostics); } } + + match compilation_unit.type_check(&symbol_table) { + Ok(_) => {} + Err(diagnostics) => { + panic!("{:?}", diagnostics); + } + } } } diff --git a/dmc-lib/src/ast/constructor.rs b/dmc-lib/src/ast/constructor.rs index 796f57b..925f595 100644 --- a/dmc-lib/src/ast/constructor.rs +++ b/dmc-lib/src/ast/constructor.rs @@ -30,6 +30,10 @@ impl Constructor { } } + pub fn statements(&self) -> &[Statement] { + &self.statements + } + pub fn gather_declared_names( &mut self, symbol_table: &mut SymbolTable, @@ -124,4 +128,32 @@ impl Constructor { Err(statements_diagnostics) } } + + pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec> { + let parameters_diagnostics: Vec = self + .parameters + .iter_mut() + .map(|param| param.type_check(symbol_table)) + .filter_map(Result::err) + .flatten() + .collect(); + + if !parameters_diagnostics.is_empty() { + return Err(parameters_diagnostics); + } + + let statements_diagnostics: Vec = self + .statements + .iter_mut() + .map(|statement| statement.type_check(symbol_table, None)) + .filter_map(Result::err) + .flatten() + .collect(); + + if statements_diagnostics.is_empty() { + Ok(()) + } else { + Err(statements_diagnostics) + } + } } diff --git a/dmc-lib/src/ast/expression_statement.rs b/dmc-lib/src/ast/expression_statement.rs index 1772320..7929468 100644 --- a/dmc-lib/src/ast/expression_statement.rs +++ b/dmc-lib/src/ast/expression_statement.rs @@ -3,10 +3,8 @@ use crate::ast::ir_builder::IrBuilder; use crate::diagnostic::Diagnostic; use crate::ir::ir_return::IrReturn; use crate::ir::ir_statement::IrStatement; -use crate::symbol::function_symbol::FunctionSymbol; use crate::symbol_table::SymbolTable; -use std::cell::RefCell; -use std::rc::Rc; +use crate::type_info::TypeInfo; pub struct ExpressionStatement { expression: Box, @@ -37,15 +35,13 @@ impl ExpressionStatement { pub fn type_check( &mut self, symbol_table: &SymbolTable, - is_last: bool, - function_symbol: &Rc>, + must_return_type_info: Option<&TypeInfo>, ) -> Result<(), Vec> { self.expression.type_check(symbol_table)?; - if is_last { + if must_return_type_info.is_some() { let expression_type = self.expression.type_info(); - let borrowed_symbol = function_symbol.borrow(); - let return_type = borrowed_symbol.return_type_info(); + let return_type = must_return_type_info.unwrap(); if !return_type.is_assignable_from(expression_type) { return Err(vec![Diagnostic::new( &format!( diff --git a/dmc-lib/src/ast/field.rs b/dmc-lib/src/ast/field.rs index 0878b65..0e1b6bd 100644 --- a/dmc-lib/src/ast/field.rs +++ b/dmc-lib/src/ast/field.rs @@ -37,6 +37,18 @@ impl Field { } } + pub fn declared_name(&self) -> &str { + &self.declared_name + } + + pub fn declared_name_source_range(&self) -> &SourceRange { + &self.declared_name_source_range + } + + pub fn initializer(&self) -> Option<&Expression> { + self.initializer.as_ref().map(Box::as_ref) + } + pub fn gather_declared_names( &mut self, symbol_table: &mut SymbolTable, diff --git a/dmc-lib/src/ast/function.rs b/dmc-lib/src/ast/function.rs index fd17249..764ccae 100644 --- a/dmc-lib/src/ast/function.rs +++ b/dmc-lib/src/ast/function.rs @@ -208,6 +208,7 @@ impl Function { ); let function_symbol = self.function_symbol.as_ref().unwrap(); + let return_type_info = function_symbol.borrow().return_type_info().clone(); let statements_len = self.statements.len(); // statements @@ -218,7 +219,11 @@ impl Function { .enumerate() .map(|(i, statement)| { let is_last = i == statements_len - 1; - statement.type_check(symbol_table, is_last, function_symbol) + if is_last { + statement.type_check(symbol_table, Some(&return_type_info)) + } else { + statement.type_check(symbol_table, None) + } }) .filter_map(Result::err) .flatten() diff --git a/dmc-lib/src/ast/parameter.rs b/dmc-lib/src/ast/parameter.rs index 0a1614d..09cf77c 100644 --- a/dmc-lib/src/ast/parameter.rs +++ b/dmc-lib/src/ast/parameter.rs @@ -36,7 +36,7 @@ impl Parameter { let insert_result = symbol_table.insert_parameter_symbol(ParameterSymbol::new( &self.declared_name, self.declared_name_source_range.clone(), - TypeInfo::from_declared_name(self.type_use.declared_name()), + TypeInfo::from_declared_name(self.type_use.declared_name()), // todo: this will blow up if type is a Class )); match insert_result { Ok(parameter_symbol) => { diff --git a/dmc-lib/src/ast/statement.rs b/dmc-lib/src/ast/statement.rs index 03abe73..b6f7428 100644 --- a/dmc-lib/src/ast/statement.rs +++ b/dmc-lib/src/ast/statement.rs @@ -2,10 +2,8 @@ use crate::ast::expression_statement::ExpressionStatement; use crate::ast::ir_builder::IrBuilder; use crate::ast::let_statement::LetStatement; use crate::diagnostic::Diagnostic; -use crate::symbol::function_symbol::FunctionSymbol; use crate::symbol_table::SymbolTable; -use std::cell::RefCell; -use std::rc::Rc; +use crate::type_info::TypeInfo; pub enum Statement { Let(LetStatement), @@ -37,13 +35,12 @@ impl Statement { pub fn type_check( &mut self, symbol_table: &SymbolTable, - is_last: bool, - function_symbol: &Rc>, + must_return_type_info: Option<&TypeInfo>, ) -> Result<(), Vec> { match self { Statement::Let(let_statement) => let_statement.type_check(symbol_table), Statement::Expression(expression_statement) => { - expression_statement.type_check(symbol_table, is_last, function_symbol) + expression_statement.type_check(symbol_table, must_return_type_info) } } } diff --git a/dmc-lib/src/ast/type_use.rs b/dmc-lib/src/ast/type_use.rs index b4e362a..8f049c7 100644 --- a/dmc-lib/src/ast/type_use.rs +++ b/dmc-lib/src/ast/type_use.rs @@ -56,7 +56,7 @@ impl TypeUse { } } - pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec> { + pub fn type_check(&mut self, _symbol_table: &SymbolTable) -> Result<(), Vec> { Ok(()) // no-op, for now }