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, declared_name_source_range: SourceRange, constructor: Option, fields: Vec, functions: Vec, class_symbol: Option>>, } impl Class { pub fn new( declared_name: &str, declared_name_source_range: SourceRange, constructor: Option, fields: Vec, functions: Vec, ) -> 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> { // 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 = 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 = 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> { self.constructor .as_mut() .map(|constructor| { constructor.check_name_usages(symbol_table, self.class_symbol.as_ref()) }) .transpose()?; let fields_diagnostics: Vec = 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 = 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> { let mut diagnostics: Vec = 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) { let mut ir_functions: Vec = 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) } }