Add type-checking to classes/fields/constructors.

This commit is contained in:
Jesse Brault 2026-03-12 15:37:37 -05:00
parent 3c0bf948ac
commit 8082f4c2e6
8 changed files with 121 additions and 24 deletions

View File

@ -5,6 +5,7 @@ use crate::diagnostic::Diagnostic;
use crate::source_range::SourceRange; use crate::source_range::SourceRange;
use crate::symbol::class_symbol::ClassSymbol; use crate::symbol::class_symbol::ClassSymbol;
use crate::symbol_table::{SymbolInsertError, SymbolTable}; use crate::symbol_table::{SymbolInsertError, SymbolTable};
use std::collections::HashSet;
use std::rc::Rc; use std::rc::Rc;
pub struct Class { pub struct Class {
@ -138,10 +139,6 @@ impl Class {
} }
pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec<Diagnostic>> { pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec<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 diagnostics: Vec<Diagnostic> = vec![]; let mut diagnostics: Vec<Diagnostic> = vec![];
self.fields self.fields
@ -151,7 +148,58 @@ impl Class {
.flatten() .flatten()
.for_each(|diagnostic| diagnostics.push(diagnostic)); .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() { if diagnostics.is_empty() {
Ok(()) Ok(())
@ -167,11 +215,11 @@ mod tests {
use crate::parser::parse_compilation_unit; use crate::parser::parse_compilation_unit;
#[test] #[test]
fn name_analysis_no_diagnostics() { fn complete_example_no_diagnostics() {
let parse_result = parse_compilation_unit( let parse_result = parse_compilation_unit(
" "
class Foo class Foo
mut bar: Int mut bar: Int = 42
ctor(_bar: Int) ctor(_bar: Int)
end end
@ -210,5 +258,12 @@ mod tests {
panic!("{:?}", diagnostics); panic!("{:?}", diagnostics);
} }
} }
match compilation_unit.type_check(&symbol_table) {
Ok(_) => {}
Err(diagnostics) => {
panic!("{:?}", diagnostics);
}
}
} }
} }

View File

@ -30,6 +30,10 @@ impl Constructor {
} }
} }
pub fn statements(&self) -> &[Statement] {
&self.statements
}
pub fn gather_declared_names( pub fn gather_declared_names(
&mut self, &mut self,
symbol_table: &mut SymbolTable, symbol_table: &mut SymbolTable,
@ -124,4 +128,32 @@ impl Constructor {
Err(statements_diagnostics) Err(statements_diagnostics)
} }
} }
pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec<Diagnostic>> {
let parameters_diagnostics: Vec<Diagnostic> = 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<Diagnostic> = 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)
}
}
} }

View File

@ -3,10 +3,8 @@ use crate::ast::ir_builder::IrBuilder;
use crate::diagnostic::Diagnostic; use crate::diagnostic::Diagnostic;
use crate::ir::ir_return::IrReturn; use crate::ir::ir_return::IrReturn;
use crate::ir::ir_statement::IrStatement; use crate::ir::ir_statement::IrStatement;
use crate::symbol::function_symbol::FunctionSymbol;
use crate::symbol_table::SymbolTable; use crate::symbol_table::SymbolTable;
use std::cell::RefCell; use crate::type_info::TypeInfo;
use std::rc::Rc;
pub struct ExpressionStatement { pub struct ExpressionStatement {
expression: Box<Expression>, expression: Box<Expression>,
@ -37,15 +35,13 @@ impl ExpressionStatement {
pub fn type_check( pub fn type_check(
&mut self, &mut self,
symbol_table: &SymbolTable, symbol_table: &SymbolTable,
is_last: bool, must_return_type_info: Option<&TypeInfo>,
function_symbol: &Rc<RefCell<FunctionSymbol>>,
) -> Result<(), Vec<Diagnostic>> { ) -> Result<(), Vec<Diagnostic>> {
self.expression.type_check(symbol_table)?; self.expression.type_check(symbol_table)?;
if is_last { if must_return_type_info.is_some() {
let expression_type = self.expression.type_info(); let expression_type = self.expression.type_info();
let borrowed_symbol = function_symbol.borrow(); let return_type = must_return_type_info.unwrap();
let return_type = borrowed_symbol.return_type_info();
if !return_type.is_assignable_from(expression_type) { if !return_type.is_assignable_from(expression_type) {
return Err(vec![Diagnostic::new( return Err(vec![Diagnostic::new(
&format!( &format!(

View File

@ -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( pub fn gather_declared_names(
&mut self, &mut self,
symbol_table: &mut SymbolTable, symbol_table: &mut SymbolTable,

View File

@ -208,6 +208,7 @@ impl Function {
); );
let function_symbol = self.function_symbol.as_ref().unwrap(); 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(); let statements_len = self.statements.len();
// statements // statements
@ -218,7 +219,11 @@ impl Function {
.enumerate() .enumerate()
.map(|(i, statement)| { .map(|(i, statement)| {
let is_last = i == statements_len - 1; 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) .filter_map(Result::err)
.flatten() .flatten()

View File

@ -36,7 +36,7 @@ impl Parameter {
let insert_result = symbol_table.insert_parameter_symbol(ParameterSymbol::new( let insert_result = symbol_table.insert_parameter_symbol(ParameterSymbol::new(
&self.declared_name, &self.declared_name,
self.declared_name_source_range.clone(), 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 { match insert_result {
Ok(parameter_symbol) => { Ok(parameter_symbol) => {

View File

@ -2,10 +2,8 @@ use crate::ast::expression_statement::ExpressionStatement;
use crate::ast::ir_builder::IrBuilder; use crate::ast::ir_builder::IrBuilder;
use crate::ast::let_statement::LetStatement; use crate::ast::let_statement::LetStatement;
use crate::diagnostic::Diagnostic; use crate::diagnostic::Diagnostic;
use crate::symbol::function_symbol::FunctionSymbol;
use crate::symbol_table::SymbolTable; use crate::symbol_table::SymbolTable;
use std::cell::RefCell; use crate::type_info::TypeInfo;
use std::rc::Rc;
pub enum Statement { pub enum Statement {
Let(LetStatement), Let(LetStatement),
@ -37,13 +35,12 @@ impl Statement {
pub fn type_check( pub fn type_check(
&mut self, &mut self,
symbol_table: &SymbolTable, symbol_table: &SymbolTable,
is_last: bool, must_return_type_info: Option<&TypeInfo>,
function_symbol: &Rc<RefCell<FunctionSymbol>>,
) -> Result<(), Vec<Diagnostic>> { ) -> Result<(), Vec<Diagnostic>> {
match self { match self {
Statement::Let(let_statement) => let_statement.type_check(symbol_table), Statement::Let(let_statement) => let_statement.type_check(symbol_table),
Statement::Expression(expression_statement) => { Statement::Expression(expression_statement) => {
expression_statement.type_check(symbol_table, is_last, function_symbol) expression_statement.type_check(symbol_table, must_return_type_info)
} }
} }
} }

View File

@ -56,7 +56,7 @@ impl TypeUse {
} }
} }
pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec<Diagnostic>> { pub fn type_check(&mut self, _symbol_table: &SymbolTable) -> Result<(), Vec<Diagnostic>> {
Ok(()) // no-op, for now Ok(()) // no-op, for now
} }