deimos-lang/dmc-lib/src/ast/class.rs
2026-03-13 20:21:49 -05:00

304 lines
9.9 KiB
Rust

use crate::ast::constructor::Constructor;
use crate::ast::field::Field;
use crate::ast::fqn_context::FqnContext;
use crate::ast::fqn_util::fqn_parts_to_string;
use crate::ast::function::Function;
use crate::diagnostic::{Diagnostic, SecondaryLabel};
use crate::ir::ir_class::{IrClass, IrField};
use crate::ir::ir_function::IrFunction;
use crate::source_range::SourceRange;
use crate::symbol::Symbol;
use crate::symbol::class_symbol::ClassSymbol;
use crate::symbol_table::{SymbolInsertError, SymbolTable};
use std::cell::RefCell;
use std::collections::HashSet;
use std::rc::Rc;
pub struct Class {
declared_name: Rc<str>,
declared_name_source_range: SourceRange,
constructor: Option<Constructor>,
fields: Vec<Field>,
functions: Vec<Function>,
class_symbol: Option<Rc<RefCell<ClassSymbol>>>,
}
impl Class {
pub fn new(
declared_name: &str,
declared_name_source_range: SourceRange,
constructor: Option<Constructor>,
fields: Vec<Field>,
functions: Vec<Function>,
) -> Self {
Class {
declared_name: declared_name.into(),
declared_name_source_range,
constructor,
fields,
functions,
class_symbol: None,
}
}
pub fn gather_declared_names(
&mut self,
symbol_table: &mut SymbolTable,
fqn_context: &mut FqnContext,
) -> Result<(), Vec<Diagnostic>> {
// 1. insert class symbol
let to_insert = ClassSymbol::new(
&self.declared_name,
self.declared_name_source_range.clone(),
fqn_context.resolve(&self.declared_name),
false,
);
// 1a. Push class name on fqn
fqn_context.push(self.declared_name.clone());
let class_symbol = symbol_table
.insert_class_symbol(to_insert)
.map_err(|e| match e {
SymbolInsertError::AlreadyDeclared(already_declared) => {
let symbol = already_declared.symbol().borrow();
vec![
Diagnostic::new(
&format!(
"Symbol {} already declared in current scope.",
already_declared.symbol().borrow().declared_name(),
),
self.declared_name_source_range.start(),
self.declared_name_source_range.end(),
)
.with_secondary_labels(&[SecondaryLabel::new(
symbol.declared_name_source_range().start(),
symbol.declared_name_source_range().end(),
Some("Symbol declared here.".to_string()),
)])
.with_reporter(file!(), line!()),
]
}
})?;
// save symbol for later
self.class_symbol = Some(class_symbol);
// 2. push scope
symbol_table.push_class_scope(&format!("class_scope({})", self.declared_name));
// 3. gather fields
let fields_diagnostics: Vec<Diagnostic> = self
.fields
.iter_mut()
.enumerate()
.map(|(field_index, field)| field.gather_declared_names(symbol_table, field_index))
.filter_map(Result::err)
.flatten()
.collect();
if !fields_diagnostics.is_empty() {
return Err(fields_diagnostics);
}
// 4. gather constructor
if let Some(constructor) = &mut self.constructor {
let constructor_symbol =
constructor.gather_declared_names(symbol_table, fqn_context)?;
self.class_symbol
.as_mut()
.unwrap()
.borrow_mut()
.set_constructor_symbol(Some(constructor_symbol));
}
// 5. gather functions
// note: for each function, insert at index 0 a self parameter
let functions_diagnostics: Vec<Diagnostic> = self
.functions
.iter_mut()
.map(|function| {
function.gather_declared_names(
symbol_table,
fqn_context,
self.class_symbol.as_ref(),
)
})
.filter_map(Result::err)
.flatten()
.collect();
if !functions_diagnostics.is_empty() {
return Err(functions_diagnostics);
}
// 6. pop scope
symbol_table.pop_scope();
// 7. pop fqn part
fqn_context.pop();
Ok(())
}
pub fn check_name_usages(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec<Diagnostic>> {
self.constructor
.as_mut()
.map(|constructor| {
constructor.check_name_usages(symbol_table, self.class_symbol.as_ref())
})
.transpose()?;
let fields_diagnostics: Vec<Diagnostic> = self
.fields
.iter_mut()
.map(|field| field.check_name_usages(symbol_table, self.class_symbol.as_ref()))
.filter_map(Result::err)
.flatten()
.collect();
if !fields_diagnostics.is_empty() {
return Err(fields_diagnostics);
}
if let Some(constructor) = &mut self.constructor {
constructor.check_name_usages(symbol_table, self.class_symbol.as_ref())?;
}
let functions_diagnostics: Vec<Diagnostic> = self
.functions
.iter_mut()
.map(|function| function.check_name_usages(symbol_table, self.class_symbol.as_ref()))
.filter_map(Result::err)
.flatten()
.collect();
if functions_diagnostics.is_empty() {
Ok(())
} else {
Err(functions_diagnostics)
}
}
pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec<Diagnostic>> {
let mut diagnostics: Vec<Diagnostic> = vec![];
self.fields
.iter_mut()
.map(|field| field.type_check(symbol_table))
.filter_map(Result::err)
.flatten()
.for_each(|diagnostic| diagnostics.push(diagnostic));
if !diagnostics.is_empty() {
return Err(diagnostics);
}
if let Some(constructor) = &mut self.constructor {
match constructor.type_check(symbol_table) {
Ok(_) => {}
Err(mut constructor_diagnostics) => {
diagnostics.append(&mut constructor_diagnostics);
}
}
}
if !diagnostics.is_empty() {
return Err(diagnostics);
}
self.functions
.iter_mut()
.map(|function| function.type_check(symbol_table))
.filter_map(Result::err)
.flatten()
.for_each(|diagnostic| diagnostics.push(diagnostic));
if !diagnostics.is_empty() {
return Err(diagnostics);
}
// 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_primary_label_message(
"Must be initialized in declaration or constructor.",
)
.with_reporter(file!(), line!()),
)
}
}
if diagnostics.is_empty() {
Ok(())
} else {
Err(diagnostics)
}
}
pub fn to_ir(&self, symbol_table: &SymbolTable) -> (IrClass, Vec<IrFunction>) {
let mut ir_functions: Vec<IrFunction> = vec![];
if let Some(constructor) = &self.constructor {
ir_functions.push(constructor.to_ir(
self.class_symbol.as_ref().unwrap(),
&self.fields,
symbol_table,
))
}
for function in &self.functions {
ir_functions.push(function.to_ir(
symbol_table,
Some(self.class_symbol.as_ref().unwrap().clone()),
));
}
let class_symbol = self.class_symbol.as_ref().unwrap().borrow();
let ir_class = IrClass::new(
class_symbol.declared_name_owned(),
fqn_parts_to_string(class_symbol.fqn_parts()).into(),
self.fields
.iter()
.map(|field| {
IrField::new(
field.declared_name().into(),
field.field_symbol().borrow().field_index(),
field.field_symbol().borrow().type_info().clone(),
)
})
.collect(),
);
(ir_class, ir_functions)
}
}