From 8724c07ae24798438d68db5c7dd9773058588b35 Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Wed, 18 Mar 2026 15:12:51 -0500 Subject: [PATCH] Refactor class ast node. --- dmc-lib/src/ast/class.rs | 626 ++++++++++++++--------- dmc-lib/src/ast/compilation_unit.rs | 20 +- dmc-lib/src/ast/constructor.rs | 4 +- dmc-lib/src/ast/extern_function.rs | 6 +- dmc-lib/src/ast/function.rs | 15 +- dmc-lib/src/ast/helpers.rs | 61 ++- dmc-lib/src/ast/util.rs | 21 + dmc-lib/src/error_codes.rs | 2 + dmc-lib/src/symbol/class_symbol.rs | 7 +- dmc-lib/src/symbol/constructor_symbol.rs | 11 +- 10 files changed, 493 insertions(+), 280 deletions(-) diff --git a/dmc-lib/src/ast/class.rs b/dmc-lib/src/ast/class.rs index f7809fa..1a5b846 100644 --- a/dmc-lib/src/ast/class.rs +++ b/dmc-lib/src/ast/class.rs @@ -1,3 +1,4 @@ +use crate::ast::assign_statement::AssignStatement; use crate::ast::constructor::Constructor; use crate::ast::expression::Expression; use crate::ast::field::Field; @@ -5,18 +6,24 @@ use crate::ast::fqn_context::FqnContext; use crate::ast::fqn_util::fqn_parts_to_string; use crate::ast::function::Function; use crate::ast::generic_parameter::GenericParameter; +use crate::ast::helpers::{ + collect_diagnostics_mut, collect_diagnostics_single, map_symbol_insert_result, + resolve_ctor_name, +}; use crate::ast::statement::Statement; -use crate::diagnostic::{Diagnostic, SecondaryLabel}; +use crate::diagnostic::Diagnostic; +use crate::error_codes::{FIELD_MULTIPLE_INIT, FIELD_UNINIT}; use crate::ir::ir_class::{IrClass, IrField}; use crate::ir::ir_function::IrFunction; -use crate::maybe_return_diagnostics; use crate::source_range::SourceRange; +use crate::symbol::Symbol; use crate::symbol::class_symbol::ClassSymbol; +use crate::symbol::constructor_symbol::ConstructorSymbol; use crate::symbol::field_symbol::FieldSymbol; use crate::symbol::generic_parameter_symbol::GenericParameterSymbol; -use crate::symbol::Symbol; -use crate::symbol_table::{SymbolInsertError, SymbolTable}; -use std::cell::RefCell; +use crate::symbol_table::SymbolTable; +use crate::{ok_or_err_diagnostics, push_ok_or_push_errs}; +use std::cell::{Ref, RefCell, RefMut}; use std::collections::{HashMap, HashSet}; use std::rc::Rc; @@ -39,7 +46,7 @@ impl Class { fields: Vec, functions: Vec, ) -> Self { - Class { + Self { declared_name: declared_name.into(), declared_name_source_range, generic_parameters, @@ -50,284 +57,419 @@ impl Class { } } + /// Returns a [ClassSymbol] representing this class. + fn make_class_symbol(&self, fqn_context: &FqnContext) -> ClassSymbol { + ClassSymbol::new( + &self.declared_name, + self.declared_name_source_range.clone(), + fqn_context.resolve(&self.declared_name), + false, + ) + } + + /// Inserts the given [ClassSymbol] into the `symbol_table`, returning `Ok` if the insert was + /// successful, else `Err` with diagnostics. + fn insert_class_symbol( + &mut self, + symbol_table: &mut SymbolTable, + class_symbol: ClassSymbol, + ) -> Result>, Vec> { + map_symbol_insert_result( + symbol_table.insert_class_symbol(class_symbol), + &self.declared_name_source_range, + ) + } + + /// Sets this class' class symbol to the given symbol. + fn set_class_symbol(&mut self, symbol: Rc>) { + self.class_symbol = Some(symbol); + } + + /// Gathers names of this class' generic parameters, returning `Ok` if all + /// [GenericParameter]s produced an `Ok(GenericParameterSymbol)`, else `Err`. + fn gather_generic_parameters_names( + &mut self, + symbol_table: &mut SymbolTable, + ) -> Result>>, Vec> { + let mut symbols: Vec>> = vec![]; + let mut diagnostics: Vec = vec![]; + + for generic_parameter in &mut self.generic_parameters { + push_ok_or_push_errs!( + generic_parameter.gather_declared_names(symbol_table), + symbols, + diagnostics + ); + } + + ok_or_err_diagnostics!(symbols, diagnostics) + } + + /// Returns a [Ref] to this class' [ClassSymbol]. Only safe to call if the class symbol has been + /// set. + fn use_class_symbol(&self) -> Ref { + self.class_symbol.as_ref().unwrap().borrow() + } + + /// Returns a [RefMut] to this class' [ClassSymbol]. Only safe to call if the class symbol has + /// been set. + fn use_class_symbol_mut(&mut self) -> RefMut { + self.class_symbol.as_mut().unwrap().borrow_mut() + } + + /// Sets the given [GenericParameterSymbol]s on this class' [ClassSymbol]. + fn set_generic_parameters_on_class_symbol( + &mut self, + generic_parameter_symbols: Vec>>, + ) { + self.use_class_symbol_mut() + .set_generic_parameters(generic_parameter_symbols); + } + + /// Returns `Ok` of [FieldSymbol]s if all of this class' fields [Field::gather_declared_names] + /// methods return `Ok`, else `Err` of [Diagnostic]s. + fn gather_fields_names( + &mut self, + symbol_table: &mut SymbolTable, + ) -> Result>>, Vec> { + let mut symbols: Vec>> = vec![]; + let mut diagnostics: Vec = vec![]; + + for (field_index, field) in self.fields.iter_mut().enumerate() { + push_ok_or_push_errs!( + field.gather_declared_names(symbol_table, field_index), + symbols, + diagnostics + ); + } + + ok_or_err_diagnostics!(symbols, diagnostics) + } + + /// Converts the given `field_symbols` [Vec] into a [HashMap] of field names to [FieldSymbol]s. + fn make_fields_hash_map( + field_symbols: Vec>>, + ) -> HashMap, Rc>> { + HashMap::from_iter( + field_symbols + .into_iter() + .map(|field| { + let name = field.borrow().declared_name_owned(); + (name, field) + }) + .collect::>(), + ) + } + + /// Sets the field symbols property on the class' [ClassSymbol]. + fn set_field_symbols_on_class_symbol(&mut self, field_symbols: Vec>>) { + self.use_class_symbol_mut() + .set_fields(Self::make_fields_hash_map(field_symbols)) + } + + /// Returns a [ConstructorSymbol] representing a default constructor appropriate for this class. + fn make_default_constructor(&self, fqn_context: &FqnContext) -> ConstructorSymbol { + ConstructorSymbol::new( + self.declared_name_source_range.clone(), + resolve_ctor_name(fqn_context), + false, + true, + ) + } + + /// Gathers names in the class' [Constructor] and returns a [ConstructorSymbol], if the + /// constructor is present; else returns a symbol defining a default constructor. + fn gather_constructor_names( + &mut self, + symbol_table: &mut SymbolTable, + fqn_context: &FqnContext, + ) -> Result>, Vec> { + if let Some(constructor) = &mut self.constructor { + constructor.gather_declared_names(symbol_table, fqn_context) + } else { + Ok(Rc::new(RefCell::new( + self.make_default_constructor(fqn_context), + ))) + } + } + + /// Sets the constructor symbol on this class' [ClassSymbol]. + fn set_constructor_symbol_on_class_symbol( + &mut self, + constructor_symbol: Rc>, + ) { + self.use_class_symbol_mut() + .set_constructor_symbol(constructor_symbol); + } + + /// Gathers function names and returns `Ok` if no diagnostics, else `Err`. + fn gather_functions_names( + &mut self, + symbol_table: &mut SymbolTable, + fqn_context: &FqnContext, + ) -> Result<(), Vec> { + collect_diagnostics_mut(&mut self.functions, |f| { + f.gather_declared_names(symbol_table, fqn_context, self.class_symbol.as_ref()) + }) + } + 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, - ); + // 1. Make and insert class symbol + let to_insert = self.make_class_symbol(fqn_context); + let class_symbol = self.insert_class_symbol(symbol_table, to_insert)?; + self.set_class_symbol(class_symbol); - // 1a. Push class name on fqn + // 2. Push class name on fqn context 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 + // 3. push scope symbol_table.push_class_scope(&format!("class_scope({})", self.declared_name)); - // 3a. gather generic parameters - let mut generic_parameter_symbols: Vec>> = vec![]; - let mut generic_parameter_diagnostics: Vec = vec![]; - for generic_parameter in &mut self.generic_parameters { - match generic_parameter.gather_declared_names(symbol_table) { - Ok(generic_parameter_symbol) => { - generic_parameter_symbols.push(generic_parameter_symbol); - } - Err(mut d) => { - generic_parameter_diagnostics.append(&mut d); - } - } - } + // 4. gather generic parameters + let generic_parameter_symbols = self + .gather_generic_parameters_names(symbol_table) + .inspect_err(|_| { + symbol_table.pop_scope(); + fqn_context.pop(); + })?; + self.set_generic_parameters_on_class_symbol(generic_parameter_symbols); - maybe_return_diagnostics!(generic_parameter_diagnostics); - // save generics to class symbol - self.class_symbol - .as_mut() - .unwrap() - .borrow_mut() - .set_generic_parameters(generic_parameter_symbols); + // 5. gather fields + let field_symbols = self.gather_fields_names(symbol_table).inspect_err(|_| { + symbol_table.pop_scope(); + fqn_context.pop(); + })?; + self.set_field_symbols_on_class_symbol(field_symbols); - // 3b. gather fields - let mut field_symbols: HashMap, Rc>> = HashMap::new(); - let mut fields_diagnostics: Vec = vec![]; + // 6. gather constructor + let constructor_symbol = self + .gather_constructor_names(symbol_table, fqn_context) + .inspect_err(|_| { + symbol_table.pop_scope(); + fqn_context.pop(); + })?; + self.set_constructor_symbol_on_class_symbol(constructor_symbol); - for (index, field) in self.fields.iter_mut().enumerate() { - match field.gather_declared_names(symbol_table, index) { - Ok(field_symbol) => { - field_symbols.insert(field.declared_name_owned(), field_symbol); - } - Err(mut d) => { - fields_diagnostics.append(&mut d); - } - }; - } + // 7. gather functions + self.gather_functions_names(symbol_table, fqn_context) + .inspect_err(|_| { + symbol_table.pop_scope(); + fqn_context.pop(); + })?; - if !fields_diagnostics.is_empty() { - return Err(fields_diagnostics); - } else { - self.class_symbol - .as_mut() - .unwrap() - .borrow_mut() - .set_fields(field_symbols); - } - - // 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 + // 8. pop scope symbol_table.pop_scope(); - // 7. pop fqn part + // 9. pop fqn part fqn_context.pop(); Ok(()) } - pub fn check_name_usages(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec> { - let generics_diagnostics: Vec = self - .generic_parameters - .iter_mut() - .map(|generic_parameter| { - generic_parameter.check_name_usages(symbol_table, self.class_symbol.as_ref()) - }) - .filter_map(Result::err) - .flatten() - .collect(); - maybe_return_diagnostics!(generics_diagnostics); - - 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); - } + fn check_generics_names(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec> { + collect_diagnostics_mut(&mut self.generic_parameters, |gp| { + gp.check_name_usages(symbol_table, self.class_symbol.as_ref()) + }) + } + /// Checks the [Constructor]'s name usages if present. + fn check_constructor_names( + &mut self, + symbol_table: &SymbolTable, + ) -> Result<(), Vec> { if let Some(constructor) = &mut self.constructor { - constructor.check_name_usages(symbol_table, self.class_symbol.as_ref())?; + constructor.check_name_usages(symbol_table, self.class_symbol.as_ref())? } + Ok(()) + } - 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(); + fn check_fields_names(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec> { + collect_diagnostics_mut(&mut self.fields, |f| { + f.check_name_usages(symbol_table, self.class_symbol.as_ref()) + }) + } - if functions_diagnostics.is_empty() { - Ok(()) - } else { - Err(functions_diagnostics) + fn check_functions_names(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec> { + collect_diagnostics_mut(&mut self.functions, |f| { + f.check_name_usages(symbol_table, self.class_symbol.as_ref()) + }) + } + + pub fn check_name_usages(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec> { + self.check_generics_names(symbol_table)?; + self.check_constructor_names(symbol_table)?; + self.check_fields_names(symbol_table)?; + self.check_functions_names(symbol_table)?; + Ok(()) + } + + fn type_check_generics(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec> { + collect_diagnostics_mut(&mut self.generic_parameters, |gp| { + gp.type_check(symbol_table) + }) + } + + fn type_check_fields(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec> { + collect_diagnostics_mut(&mut self.fields, |f| f.type_check(symbol_table)) + } + + fn type_check_constructor( + &mut self, + symbol_table: &SymbolTable, + ) -> Result<(), Vec> { + if let Some(constructor) = &mut self.constructor { + constructor.type_check(symbol_table)?; + } + Ok(()) + } + + fn type_check_functions(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec> { + collect_diagnostics_mut(&mut self.functions, |f| f.type_check(symbol_table)) + } + + /// Returns all field names with declared initializers. + fn field_names_with_initializers(&self) -> HashSet<&str> { + let mut set: HashSet<&str> = HashSet::new(); + for field in &self.fields { + if field.initializer().is_some() { + set.insert(field.declared_name()); + } + } + set + } + + /// If the destination of the given [AssignStatement] matches a field, returns an + /// `Ok(Some(field_name))` only if the field is not already in the `fields_already_init` set, + /// AND, if the field is immutable, the field is not initialized more than once in the + /// constructor. Otherwise, returns an `Err(Diagnostic)`. + fn check_ctor_assign_statement<'a>( + &self, + assign_statement: &'a AssignStatement, + fields_already_init: &HashSet<&&str>, + ) -> Result, Diagnostic> { + match assign_statement.destination() { + Expression::Identifier(identifier) => { + // find matching field symbol, if there is one + if let Some(field_symbol) = self.use_class_symbol().fields().get(identifier.name()) + { + // check that we don't init more than once IF field is immutable + if fields_already_init.contains(&identifier.name()) + && !field_symbol.borrow().is_mut() + { + let diagnostic = Diagnostic::new( + &format!("Immutable field {} cannot be initialized more than once in constructor.", identifier.name()), + identifier.source_range().start(), + identifier.source_range().end(), + ).with_reporter(file!(), line!()) + .with_error_code(FIELD_MULTIPLE_INIT); + Err(diagnostic) + } else { + Ok(Some(identifier.name())) + } + } else { + Ok(None) + } + } + _ => panic!("Found a non-L Value destination"), } } - pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec> { + /// Returns an `Ok(HashSet<&str>)` containing the names of all fields initialized in the + /// constructor, provided that the following are true: + /// + /// - The field is not initialized more than once in the constructor + /// - The field is not also initialized with a declared initializer. + /// + /// If the above are not met, returns `Err(diagnostics)`. + fn get_fields_init_in_ctor<'a>( + &self, + fields_with_declared_initializers: &HashSet<&str>, + ) -> Result, Vec> { + let mut constructor_inits: HashSet<&str> = HashSet::new(); 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 { - Statement::Assign(assign_statement) => match assign_statement.destination() { - Expression::Identifier(identifier) => { - if self - .class_symbol - .as_ref() - .unwrap() - .borrow() - .fields() - .contains_key(identifier.name()) - { - initialized_field_names.insert(identifier.name()); + Statement::Assign(assign_statement) => { + let fields_init_so_far = constructor_inits + .union(fields_with_declared_initializers) + .collect::>(); + match self + .check_ctor_assign_statement(assign_statement, &fields_init_so_far) + { + Ok(maybe_init_field) => match maybe_init_field { + None => {} + Some(init_field) => { + constructor_inits.insert(init_field); + } + }, + Err(diagnostic) => { + diagnostics.push(diagnostic); } } - _ => panic!("Found a non L Value assign lhs"), - }, + } _ => {} } } } + ok_or_err_diagnostics!(constructor_inits, diagnostics) + } + + /// Checks that all declared fields in this `Class` are present in the `all_inits` set. If so, + /// returns `Ok`, else `Err`. + fn check_all_fields_in_init_set( + &self, + all_inits: &HashSet<&&str>, + ) -> Result<(), Vec> { + collect_diagnostics_single(&self.fields, |field| { + if all_inits.contains(&field.declared_name()) { + Ok(()) + } else { + Err(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!()) + .with_error_code(FIELD_UNINIT)) + } + }) + } + + /// Checks that all fields are initialized, either at their declaration or in the constructor. + /// Immutable fields may be only initialized once, either at their declaration or once in the + /// constructor. Mutable fields may be initialized either at their declaration, or at least once + /// in the constructor. + fn check_field_initialization(&self) -> Result<(), Vec> { + // 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 field_names_with_initializers = self.field_names_with_initializers(); + let field_names_init_in_constructor = + self.get_fields_init_in_ctor(&field_names_with_initializers)?; + let combined = field_names_with_initializers + .union(&field_names_init_in_constructor) + .collect::>(); // 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!()), - ) - } - } + self.check_all_fields_in_init_set(&combined)?; - if diagnostics.is_empty() { - Ok(()) - } else { - Err(diagnostics) - } + Ok(()) + } + + pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec> { + self.type_check_generics(symbol_table)?; + self.type_check_fields(symbol_table)?; + self.type_check_constructor(symbol_table)?; + self.type_check_functions(symbol_table)?; + self.check_field_initialization()?; + Ok(()) } pub fn to_ir(&self, symbol_table: &SymbolTable) -> (IrClass, Vec) { diff --git a/dmc-lib/src/ast/compilation_unit.rs b/dmc-lib/src/ast/compilation_unit.rs index 5b4a3c0..bcef248 100644 --- a/dmc-lib/src/ast/compilation_unit.rs +++ b/dmc-lib/src/ast/compilation_unit.rs @@ -2,7 +2,7 @@ use crate::ast::class::Class; use crate::ast::extern_function::ExternFunction; use crate::ast::fqn_context::FqnContext; use crate::ast::function::Function; -use crate::ast::helpers::iter_phase; +use crate::ast::helpers::collect_diagnostics_into_mut; use crate::diagnostic::Diagnostic; use crate::diagnostics_result; use crate::ir::ir_class::IrClass; @@ -49,19 +49,19 @@ impl CompilationUnit { let mut fqn_context = FqnContext::new(); // in the future, we'll push the pkg/ns on here let mut diagnostics: Vec = vec![]; - iter_phase( + collect_diagnostics_into_mut( &mut self.functions, |f| f.gather_declared_names(symbol_table, &fqn_context, None), &mut diagnostics, ); - iter_phase( + collect_diagnostics_into_mut( &mut self.extern_functions, |ef| ef.gather_declared_names(symbol_table, &fqn_context), &mut diagnostics, ); - iter_phase( + collect_diagnostics_into_mut( &mut self.classes, |c| c.gather_declared_names(symbol_table, &mut fqn_context), &mut diagnostics, @@ -75,19 +75,19 @@ impl CompilationUnit { pub fn check_name_usages(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec> { let mut diagnostics: Vec = vec![]; - iter_phase( + collect_diagnostics_into_mut( &mut self.functions, |f| f.check_name_usages(symbol_table, None), &mut diagnostics, ); - iter_phase( + collect_diagnostics_into_mut( &mut self.extern_functions, |ef| ef.check_name_usages(symbol_table, None), &mut diagnostics, ); - iter_phase( + collect_diagnostics_into_mut( &mut self.classes, |c| c.check_name_usages(symbol_table), &mut diagnostics, @@ -99,19 +99,19 @@ impl CompilationUnit { pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec> { let mut diagnostics: Vec = vec![]; - iter_phase( + collect_diagnostics_into_mut( &mut self.functions, |f| f.type_check(symbol_table), &mut diagnostics, ); - iter_phase( + collect_diagnostics_into_mut( &mut self.extern_functions, |ef| ef.type_check(symbol_table), &mut diagnostics, ); - iter_phase( + collect_diagnostics_into_mut( &mut self.classes, |c| c.type_check(symbol_table), &mut diagnostics, diff --git a/dmc-lib/src/ast/constructor.rs b/dmc-lib/src/ast/constructor.rs index d1d8ec5..1ef789f 100644 --- a/dmc-lib/src/ast/constructor.rs +++ b/dmc-lib/src/ast/constructor.rs @@ -1,6 +1,7 @@ use crate::ast::field::Field; use crate::ast::fqn_context::FqnContext; use crate::ast::fqn_util::fqn_parts_to_string; +use crate::ast::helpers::resolve_ctor_name; use crate::ast::ir_builder::IrBuilder; use crate::ast::parameter::Parameter; use crate::ast::statement::Statement; @@ -62,7 +63,8 @@ impl Constructor { // insert constructor symbol let to_insert = ConstructorSymbol::new( self.ctor_keyword_source_range.clone(), - fqn_context.resolve("ctor"), // ctor is a keyword at the language level, should not be callable via normal means + resolve_ctor_name(fqn_context), + false, false, ); let constructor_symbol = diff --git a/dmc-lib/src/ast/extern_function.rs b/dmc-lib/src/ast/extern_function.rs index 29a9adf..d6538b8 100644 --- a/dmc-lib/src/ast/extern_function.rs +++ b/dmc-lib/src/ast/extern_function.rs @@ -1,5 +1,5 @@ use crate::ast::fqn_context::FqnContext; -use crate::ast::helpers::{gather_oks, iter_phase, map_symbol_insert_result}; +use crate::ast::helpers::{collect_diagnostics_into_mut, gather_oks, map_symbol_insert_result}; use crate::ast::parameter::Parameter; use crate::ast::type_use::TypeUse; use crate::diagnostic::Diagnostic; @@ -127,7 +127,7 @@ impl ExternFunction { class_context: Option<&Rc>>, diagnostics: &mut Vec, ) { - iter_phase( + collect_diagnostics_into_mut( &mut self.parameters, |p| p.check_name_usages(symbol_table, class_context), diagnostics, @@ -180,7 +180,7 @@ impl ExternFunction { symbol_table: &SymbolTable, diagnostics: &mut Vec, ) { - iter_phase( + collect_diagnostics_into_mut( &mut self.parameters, |p| p.type_check(symbol_table), diagnostics, diff --git a/dmc-lib/src/ast/function.rs b/dmc-lib/src/ast/function.rs index 00ceffb..3fb78be 100644 --- a/dmc-lib/src/ast/function.rs +++ b/dmc-lib/src/ast/function.rs @@ -1,7 +1,8 @@ use crate::ast::fqn_context::FqnContext; use crate::ast::fqn_util::fqn_parts_to_string; use crate::ast::helpers::{ - gather_oks, iter_phase, iter_phase_enumerated, map_symbol_insert_result, + collect_diagnostics_into_enumerated_mut, collect_diagnostics_into_mut, gather_oks, + map_symbol_insert_result, }; use crate::ast::ir_builder::IrBuilder; use crate::ast::parameter::Parameter; @@ -141,7 +142,7 @@ impl Function { symbol_table: &mut SymbolTable, diagnostics: &mut Vec, ) { - iter_phase( + collect_diagnostics_into_mut( self.return_type.as_mut_slice(), |type_use| type_use.gather_declared_names(symbol_table), diagnostics, @@ -154,7 +155,7 @@ impl Function { symbol_table: &mut SymbolTable, diagnostics: &mut Vec, ) { - iter_phase( + collect_diagnostics_into_mut( &mut self.statements, |s| s.gather_declared_names(symbol_table), diagnostics, @@ -198,7 +199,7 @@ impl Function { class_context: Option<&Rc>>, diagnostics: &mut Vec, ) { - iter_phase( + collect_diagnostics_into_mut( &mut self.parameters, |p| p.check_name_usages(symbol_table, class_context), diagnostics, @@ -243,7 +244,7 @@ impl Function { symbol_table: &SymbolTable, diagnostics: &mut Vec, ) { - iter_phase( + collect_diagnostics_into_mut( &mut self.statements, |s| s.check_name_usages(symbol_table), diagnostics, @@ -282,7 +283,7 @@ impl Function { symbol_table: &SymbolTable, diagnostics: &mut Vec, ) { - iter_phase( + collect_diagnostics_into_mut( &mut self.parameters, |p| p.type_check(symbol_table), diagnostics, @@ -309,7 +310,7 @@ impl Function { let return_type_info = self.get_return_type_info(); let statements_len = self.statements.len(); - iter_phase_enumerated( + collect_diagnostics_into_enumerated_mut( &mut self.statements, |i, s| { let is_last = i == statements_len - 1; diff --git a/dmc-lib/src/ast/helpers.rs b/dmc-lib/src/ast/helpers.rs index f1fd023..b4c3510 100644 --- a/dmc-lib/src/ast/helpers.rs +++ b/dmc-lib/src/ast/helpers.rs @@ -1,36 +1,77 @@ +use crate::ast::fqn_context::FqnContext; use crate::diagnostic::{Diagnostic, SecondaryLabel}; +use crate::diagnostics_result; use crate::error_codes::SYMBOL_ALREADY_DECLARED; use crate::source_range::SourceRange; use crate::symbol_table::SymbolInsertError; +use std::rc::Rc; -/// Iterates through all `ts`, running the `phase` function, and pushing returned `Diagnostic`s +/// Iterates through all `ts`, running the `f` function, and pushing returned `Diagnostic`s /// into `diagnostics`. -pub fn iter_phase( +pub fn collect_diagnostics_into_mut( ts: &mut [T], - mut phase: impl FnMut(&mut T) -> Result<(), Vec>, + mut f: impl FnMut(&mut T) -> Result<(), Vec>, diagnostics: &mut Vec, ) { ts.iter_mut() - .map(|t| phase(t)) + .map(|t| f(t)) .filter_map(Result::err) .flatten() .for_each(|d| diagnostics.push(d)); } -/// Like `iter_phase` but enumerated. -pub fn iter_phase_enumerated( +/// Like `collect_diagnostics_into` but enumerated. +pub fn collect_diagnostics_into_enumerated_mut( ts: &mut [T], - mut phase: impl FnMut(usize, &mut T) -> Result<(), Vec>, + mut f: impl FnMut(usize, &mut T) -> Result<(), Vec>, diagnostics: &mut Vec, ) { ts.iter_mut() .enumerate() - .map(|(i, t)| phase(i, t)) + .map(|(i, t)| f(i, t)) .filter_map(Result::err) .flatten() .for_each(|d| diagnostics.push(d)); } +pub fn collect_diagnostics_mut( + ts: &mut [T], + mut f: impl FnMut(&mut T) -> Result<(), Vec>, +) -> Result<(), Vec> { + let diagnostics = ts + .iter_mut() + .map(|t| f(t)) + .filter_map(Result::err) + .flatten() + .collect::>(); + diagnostics_result!(diagnostics) +} + +pub fn collect_diagnostics( + ts: &[T], + f: impl Fn(&T) -> Result<(), Vec>, +) -> Result<(), Vec> { + let diagnostics = ts + .iter() + .map(|t| f(t)) + .filter_map(Result::err) + .flatten() + .collect::>(); + diagnostics_result!(diagnostics) +} + +pub fn collect_diagnostics_single( + ts: &[T], + f: impl Fn(&T) -> Result<(), Diagnostic>, +) -> Result<(), Vec> { + let diagnostics = ts + .iter() + .map(|t| f(t)) + .filter_map(Result::err) + .collect::>(); + diagnostics_result!(diagnostics) +} + pub fn map_symbol_insert_result( insert_result: Result, declared_name_source_range: &SourceRange, @@ -77,3 +118,7 @@ pub fn gather_oks( } rs } + +pub fn resolve_ctor_name(fqn_context: &FqnContext) -> Vec> { + fqn_context.resolve("ctor") // ctor is a keyword at the language level, should not be callable via normal means +} diff --git a/dmc-lib/src/ast/util.rs b/dmc-lib/src/ast/util.rs index 12a27d0..48850e4 100644 --- a/dmc-lib/src/ast/util.rs +++ b/dmc-lib/src/ast/util.rs @@ -62,3 +62,24 @@ macro_rules! diagnostics_result { } }; } + +#[macro_export] +macro_rules! ok_or_err_diagnostics { + ( $to_return: expr, $diagnostics: expr ) => { + if $diagnostics.is_empty() { + Ok($to_return) + } else { + Err($diagnostics) + } + }; +} + +#[macro_export] +macro_rules! push_ok_or_push_errs { + ( $result: expr, $oks: expr, $errs: expr ) => { + match $result { + Ok(inner) => $oks.push(inner), + Err(mut errs) => $errs.append(&mut errs), + } + }; +} diff --git a/dmc-lib/src/error_codes.rs b/dmc-lib/src/error_codes.rs index 28c3ad2..65c7dd1 100644 --- a/dmc-lib/src/error_codes.rs +++ b/dmc-lib/src/error_codes.rs @@ -7,3 +7,5 @@ pub const ASSIGN_NO_L_VALUE: ErrorCode = 17; pub const ASSIGN_LHS_IMMUTABLE: ErrorCode = 18; pub const INCORRECT_GENERIC_ARGUMENTS: ErrorCode = 19; pub const GENERIC_ARGUMENT_TYPE_MISMATCH: ErrorCode = 20; +pub const FIELD_MULTIPLE_INIT: ErrorCode = 21; +pub const FIELD_UNINIT: ErrorCode = 22; diff --git a/dmc-lib/src/symbol/class_symbol.rs b/dmc-lib/src/symbol/class_symbol.rs index 60ac2b4..c41a989 100644 --- a/dmc-lib/src/symbol/class_symbol.rs +++ b/dmc-lib/src/symbol/class_symbol.rs @@ -53,11 +53,8 @@ impl ClassSymbol { &self.generic_parameters } - pub fn set_constructor_symbol( - &mut self, - constructor_symbol: Option>>, - ) { - self.constructor_symbol = constructor_symbol; + pub fn set_constructor_symbol(&mut self, constructor_symbol: Rc>) { + self.constructor_symbol = Some(constructor_symbol); } pub fn set_fields(&mut self, fields: HashMap, Rc>>) { diff --git a/dmc-lib/src/symbol/constructor_symbol.rs b/dmc-lib/src/symbol/constructor_symbol.rs index cb3425c..ec4e353 100644 --- a/dmc-lib/src/symbol/constructor_symbol.rs +++ b/dmc-lib/src/symbol/constructor_symbol.rs @@ -5,22 +5,25 @@ use std::cell::RefCell; use std::rc::Rc; pub struct ConstructorSymbol { - ctor_keyword_source_range: SourceRange, + keyword_source_range: SourceRange, fqn_parts: Vec>, is_extern: bool, + is_default: bool, parameters: Option>>>, } impl ConstructorSymbol { pub fn new( - ctor_keyword_source_range: SourceRange, + keyword_source_range: SourceRange, fqn_parts: Vec>, is_extern: bool, + is_default: bool, ) -> Self { Self { - ctor_keyword_source_range, + keyword_source_range, fqn_parts, is_extern, + is_default, parameters: None, } } @@ -52,6 +55,6 @@ impl Symbol for ConstructorSymbol { } fn declared_name_source_range(&self) -> &SourceRange { - &self.ctor_keyword_source_range + &self.keyword_source_range } }