deimos-lang/dmc-lib/src/ast/field.rs
2026-03-14 10:36:53 -05:00

205 lines
7.2 KiB
Rust

use crate::ast::expression::Expression;
use crate::ast::type_use::TypeUse;
use crate::diagnostic::Diagnostic;
use crate::source_range::SourceRange;
use crate::symbol::class_symbol::ClassSymbol;
use crate::symbol::field_symbol::FieldSymbol;
use crate::symbol_table::{SymbolInsertError, SymbolTable};
use std::cell::RefCell;
use std::rc::Rc;
pub struct Field {
declared_name: Rc<str>,
declared_name_source_range: SourceRange,
is_public: bool,
is_mut: bool,
declared_type: Option<Box<TypeUse>>,
initializer: Option<Box<Expression>>,
field_symbol: Option<Rc<RefCell<FieldSymbol>>>,
}
impl Field {
pub fn new(
declared_name: &str,
declared_name_source_range: SourceRange,
is_public: bool,
is_mut: bool,
declared_type: Option<TypeUse>,
initializer: Option<Expression>,
) -> Self {
Self {
declared_name: declared_name.into(),
declared_name_source_range,
is_public,
is_mut,
declared_type: declared_type.map(Box::new),
initializer: initializer.map(Box::new),
field_symbol: None,
}
}
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,
field_index: usize,
) -> Result<(), Vec<Diagnostic>> {
// 1. insert field symbol
let to_insert = FieldSymbol::new(
&self.declared_name,
self.declared_name_source_range.clone(),
self.is_mut,
);
let field_symbol = symbol_table
.insert_field_symbol(to_insert)
.map_err(|e| match e {
SymbolInsertError::AlreadyDeclared(already_declared) => {
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(),
)]
}
})?;
// save for later
self.field_symbol = Some(field_symbol);
// set field index on symbol
self.field_symbol
.as_ref()
.unwrap()
.borrow_mut()
.set_field_index(field_index);
// 2. gather type_use and initializer, if present
if let Some(type_use) = &mut self.declared_type {
type_use.gather_declared_names(symbol_table)?;
}
if let Some(initializer) = &mut self.initializer {
initializer.gather_declared_names(symbol_table)?;
}
Ok(())
}
pub fn check_name_usages(
&mut self,
symbol_table: &SymbolTable,
class_context: Option<&Rc<RefCell<ClassSymbol>>>,
) -> Result<(), Vec<Diagnostic>> {
if let Some(type_use) = &mut self.declared_type {
type_use.check_name_usages(symbol_table, class_context)?;
}
// This is going to get hairy, because users might attempt to use a field in an initializer
// (for either this field, or another one) before it's actually initialized. As such, we
// need a way to prevent lookup of current class' fields in the initializer.
// For now, the following is okay so long as we don't start referencing things in the
// initializers.
if let Some(initializer) = self.initializer.as_mut() {
initializer.check_name_usages(symbol_table)?;
}
Ok(())
}
pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec<Diagnostic>> {
let mut diagnostics: Vec<Diagnostic> = vec![];
if let Some(type_use) = &mut self.declared_type {
if let Some(mut type_use_diagnostics) = type_use.type_check(symbol_table).err() {
diagnostics.append(&mut type_use_diagnostics);
}
}
if let Some(initializer) = &mut self.initializer {
if let Some(mut initializer_diagnostics) = initializer.type_check(symbol_table).err() {
diagnostics.append(&mut initializer_diagnostics);
}
}
if !diagnostics.is_empty() {
return Err(diagnostics);
}
// Now check that types are assignable, and update field symbol's type info
let field_type_info =
match self.declared_type.as_ref() {
Some(type_use) => {
match self.initializer.as_ref() {
Some(initializer) => {
let initializer_type_info = initializer.type_info();
let declared_type_info = type_use.type_info();
if declared_type_info.is_assignable_from(initializer_type_info) {
declared_type_info
} else {
return Err(vec![
Diagnostic::new(
&format!(
"Mismatched types: {} is not assignable to {}",
initializer_type_info, declared_type_info
),
initializer.source_range().start(),
initializer.source_range().end(),
)
.with_reporter(file!(), line!()),
]);
}
}
None => {
// easy: the declared type
type_use.type_info()
}
}
}
None => {
// type is the initializer
match self.initializer.as_ref() {
Some(initializer) => initializer.type_info(),
None => {
// this is an error
return Err(vec![Diagnostic::new(
"Field must have either a declared type or initializer, or both.",
self.declared_name_source_range.start(),
self.declared_name_source_range.end(),
).with_reporter(file!(), line!())]);
}
}
}
};
// update field symbol's type info
self.field_symbol
.as_mut()
.unwrap()
.borrow_mut()
.set_type_info(field_type_info.clone());
if diagnostics.is_empty() {
Ok(())
} else {
Err(diagnostics)
}
}
pub fn field_symbol(&self) -> &Rc<RefCell<FieldSymbol>> {
self.field_symbol.as_ref().unwrap()
}
}