304 lines
9.9 KiB
Rust
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)
|
|
}
|
|
}
|