From 86fcbb494bd17ae948f82361e0c463e8060a5297 Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Mon, 16 Mar 2026 12:15:05 -0500 Subject: [PATCH] Semantic analysis of generic args and params. --- dmc-lib/src/ast/assign_statement.rs | 4 + dmc-lib/src/ast/class.rs | 93 +++++++++++-- dmc-lib/src/ast/field.rs | 5 +- dmc-lib/src/ast/function.rs | 6 + dmc-lib/src/ast/generic_parameter.rs | 120 +++++++++++++++++ dmc-lib/src/ast/mod.rs | 1 + dmc-lib/src/ast/parameter.rs | 5 +- dmc-lib/src/ast/type_use.rs | 123 ++++++++++++++++-- dmc-lib/src/error_codes.rs | 3 + dmc-lib/src/ir/ir_class.rs | 1 + dmc-lib/src/parser.rs | 52 ++++++++ dmc-lib/src/scope/class_scope.rs | 15 +++ dmc-lib/src/symbol/class_symbol.rs | 18 +++ .../src/symbol/generic_parameter_symbol.rs | 42 ++++++ dmc-lib/src/symbol/mod.rs | 1 + dmc-lib/src/symbol/type_symbol.rs | 2 + dmc-lib/src/symbol_table/helpers.rs | 10 +- dmc-lib/src/symbol_table/mod.rs | 45 ++++++- dmc-lib/src/type_info.rs | 16 +++ dvm-lib/src/vm/type_info.rs | 2 + e2e-tests/src/lib.rs | 25 ++++ 21 files changed, 559 insertions(+), 30 deletions(-) create mode 100644 dmc-lib/src/ast/generic_parameter.rs create mode 100644 dmc-lib/src/symbol/generic_parameter_symbol.rs diff --git a/dmc-lib/src/ast/assign_statement.rs b/dmc-lib/src/ast/assign_statement.rs index 8b53e5e..189cf5f 100644 --- a/dmc-lib/src/ast/assign_statement.rs +++ b/dmc-lib/src/ast/assign_statement.rs @@ -22,6 +22,10 @@ impl AssignStatement { } } + pub fn destination(&self) -> &Expression { + &self.destination + } + pub fn gather_declared_names( &mut self, symbol_table: &mut SymbolTable, diff --git a/dmc-lib/src/ast/class.rs b/dmc-lib/src/ast/class.rs index 8ff217e..87e9599 100644 --- a/dmc-lib/src/ast/class.rs +++ b/dmc-lib/src/ast/class.rs @@ -1,22 +1,30 @@ use crate::ast::constructor::Constructor; +use crate::ast::expression::Expression; 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::ast::generic_parameter::GenericParameter; +use crate::ast::statement::Statement; use crate::diagnostic::{Diagnostic, SecondaryLabel}; 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::field_symbol::FieldSymbol; +use crate::symbol::generic_parameter_symbol::GenericParameterSymbol; use crate::symbol_table::{SymbolInsertError, SymbolTable}; use std::cell::RefCell; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; +use std::process::id; use std::rc::Rc; pub struct Class { declared_name: Rc, declared_name_source_range: SourceRange, + generic_parameters: Vec, constructor: Option, fields: Vec, functions: Vec, @@ -27,6 +35,7 @@ impl Class { pub fn new( declared_name: &str, declared_name_source_range: SourceRange, + generic_parameters: Vec, constructor: Option, fields: Vec, functions: Vec, @@ -34,6 +43,7 @@ impl Class { Class { declared_name: declared_name.into(), declared_name_source_range, + generic_parameters, constructor, fields, functions, @@ -87,18 +97,51 @@ impl Class { // 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(); + // 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); + } + } + } + + 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); + + // 3b. gather fields + let mut field_symbols: HashMap, Rc>> = HashMap::new(); + let mut fields_diagnostics: Vec = vec![]; + + 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); + } + }; + } 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 @@ -142,6 +185,17 @@ impl Class { } 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| { @@ -233,9 +287,22 @@ impl Class { 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 - } + 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()); + } + } + _ => panic!("Found a non L Value assign lhs"), + }, + _ => {} } } } diff --git a/dmc-lib/src/ast/field.rs b/dmc-lib/src/ast/field.rs index 014872d..307040c 100644 --- a/dmc-lib/src/ast/field.rs +++ b/dmc-lib/src/ast/field.rs @@ -58,7 +58,7 @@ impl Field { &mut self, symbol_table: &mut SymbolTable, field_index: usize, - ) -> Result<(), Vec> { + ) -> Result>, Vec> { // 1. insert field symbol let to_insert = FieldSymbol::new( &self.declared_name, @@ -82,6 +82,7 @@ impl Field { })?; // save for later + let to_return = field_symbol.clone(); self.field_symbol = Some(field_symbol); // set field index on symbol @@ -100,7 +101,7 @@ impl Field { initializer.gather_declared_names(symbol_table)?; } - Ok(()) + Ok(to_return) } pub fn check_name_usages( diff --git a/dmc-lib/src/ast/function.rs b/dmc-lib/src/ast/function.rs index 4aa46a7..f0f7377 100644 --- a/dmc-lib/src/ast/function.rs +++ b/dmc-lib/src/ast/function.rs @@ -5,6 +5,7 @@ use crate::ast::parameter::Parameter; use crate::ast::statement::Statement; use crate::ast::type_use::TypeUse; use crate::diagnostic::Diagnostic; +use crate::handle_diagnostics; use crate::ir::ir_function::IrFunction; use crate::ir::ir_parameter::IrParameter; use crate::ir::ir_parameter_or_variable::IrParameterOrVariable; @@ -238,6 +239,11 @@ impl Function { let return_type_info = function_symbol.borrow().return_type_info().clone(); let statements_len = self.statements.len(); + // return type + if let Some(type_use) = &mut self.return_type { + handle_diagnostics!(type_use.type_check(symbol_table), diagnostics); + } + // statements diagnostics.append( &mut self diff --git a/dmc-lib/src/ast/generic_parameter.rs b/dmc-lib/src/ast/generic_parameter.rs new file mode 100644 index 0000000..bcd318e --- /dev/null +++ b/dmc-lib/src/ast/generic_parameter.rs @@ -0,0 +1,120 @@ +use crate::ast::type_use::TypeUse; +use crate::diagnostic::{Diagnostic, SecondaryLabel}; +use crate::error_codes::SYMBOL_ALREADY_DECLARED; +use crate::source_range::SourceRange; +use crate::symbol::class_symbol::ClassSymbol; +use crate::symbol::generic_parameter_symbol::GenericParameterSymbol; +use crate::symbol_table::{SymbolInsertError, SymbolTable}; +use crate::{diagnostics_result, handle_diagnostics, maybe_return_diagnostics}; +use std::cell::RefCell; +use std::rc::Rc; + +pub struct GenericParameter { + declared_name: Rc, + declared_name_source_range: SourceRange, + extends: Vec, + generic_parameter_symbol: Option>>, +} + +impl GenericParameter { + pub fn new( + declared_name: &str, + declared_name_source_range: SourceRange, + extends: Vec, + ) -> Self { + Self { + declared_name: declared_name.into(), + declared_name_source_range, + extends, + generic_parameter_symbol: None, + } + } + + pub fn gather_declared_names( + &mut self, + symbol_table: &mut SymbolTable, + ) -> Result>, Vec> { + // insert symbol + let to_insert = + GenericParameterSymbol::new(&self.declared_name, &self.declared_name_source_range); + let to_return = match symbol_table.insert_generic_parameter_symbol(to_insert) { + Ok(generic_parameter_symbol) => generic_parameter_symbol, + Err(symbol_insert_error) => { + return match symbol_insert_error { + SymbolInsertError::AlreadyDeclared(already_declared) => { + let already_declared_symbol = already_declared.symbol().borrow(); + let already_declared_source_range = + already_declared_symbol.declared_name_source_range(); + let diagnostic = Diagnostic::new( + &format!("Symbol {} already declared in scope.", self.declared_name), + self.declared_name_source_range.start(), + self.declared_name_source_range.end(), + ) + .with_reporter(file!(), line!()) + .with_error_code(SYMBOL_ALREADY_DECLARED) + .with_secondary_labels(&[SecondaryLabel::new( + already_declared_source_range.start(), + already_declared_source_range.end(), + Some("Symbol already declared here.".to_string()), + )]); + Err(vec![diagnostic]) + } + }; + } + }; + + // save param symbol + self.generic_parameter_symbol = Some(to_return.clone()); + + let mut diagnostics: Vec = vec![]; + + for type_use in &mut self.extends { + handle_diagnostics!(type_use.gather_declared_names(symbol_table), diagnostics); + } + + if diagnostics.is_empty() { + Ok(to_return) + } else { + Err(diagnostics) + } + } + + pub fn check_name_usages( + &mut self, + symbol_table: &SymbolTable, + class_context: Option<&Rc>>, + ) -> Result<(), Vec> { + let mut diagnostics: Vec = vec![]; + // check the extends type uses + for type_use in &mut self.extends { + handle_diagnostics!( + type_use.check_name_usages(symbol_table, class_context), + diagnostics + ); + } + maybe_return_diagnostics!(diagnostics); + + // now that each extends type use has type info, set the type infos on the generic parameter + let extends_type_infos = self + .extends + .iter() + .map(|type_use| type_use.type_info().clone()) + .collect::>(); + self.generic_parameter_symbol + .as_mut() + .unwrap() + .borrow_mut() + .set_extends(extends_type_infos); + + Ok(()) + } + + pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec> { + let mut diagnostics: Vec = vec![]; + // check extends type uses + for type_use in &mut self.extends { + handle_diagnostics!(type_use.type_check(symbol_table), diagnostics); + } + diagnostics_result!(diagnostics) + } +} diff --git a/dmc-lib/src/ast/mod.rs b/dmc-lib/src/ast/mod.rs index c6759bd..d969d3f 100644 --- a/dmc-lib/src/ast/mod.rs +++ b/dmc-lib/src/ast/mod.rs @@ -13,6 +13,7 @@ pub mod fqn; pub mod fqn_context; pub mod fqn_util; pub mod function; +pub mod generic_parameter; pub mod identifier; pub mod integer_literal; pub mod ir_builder; diff --git a/dmc-lib/src/ast/parameter.rs b/dmc-lib/src/ast/parameter.rs index 5a92514..1c7e535 100644 --- a/dmc-lib/src/ast/parameter.rs +++ b/dmc-lib/src/ast/parameter.rs @@ -81,8 +81,9 @@ impl Parameter { Ok(()) } - pub fn type_check(&mut self, _symbol_table: &SymbolTable) -> Result<(), Vec> { - Ok(()) // no-op for now + pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec> { + self.type_use.type_check(symbol_table)?; + Ok(()) } pub fn parameter_symbol(&self) -> &Rc> { diff --git a/dmc-lib/src/ast/type_use.rs b/dmc-lib/src/ast/type_use.rs index 9254019..58b0655 100644 --- a/dmc-lib/src/ast/type_use.rs +++ b/dmc-lib/src/ast/type_use.rs @@ -1,8 +1,10 @@ use crate::diagnostic::Diagnostic; +use crate::error_codes::INCORRECT_GENERIC_ARGUMENTS; use crate::source_range::SourceRange; use crate::symbol::class_symbol::ClassSymbol; use crate::symbol_table::SymbolTable; use crate::type_info::TypeInfo; +use crate::{diagnostics_result, handle_diagnostics, maybe_return_diagnostics}; use std::cell::RefCell; use std::rc::Rc; @@ -38,7 +40,14 @@ impl TypeUse { symbol_table: &mut SymbolTable, ) -> Result<(), Vec> { self.scope_id = Some(symbol_table.current_scope_id()); - Ok(()) + let mut inner_diagnostics: Vec = vec![]; + for generic_argument in &mut self.generic_arguments { + handle_diagnostics!( + generic_argument.gather_declared_names(symbol_table), + inner_diagnostics + ); + } + diagnostics_result!(inner_diagnostics) } pub fn check_name_usages( @@ -46,6 +55,8 @@ impl TypeUse { symbol_table: &SymbolTable, class_context: Option<&Rc>>, ) -> Result<(), Vec> { + let mut diagnostics: Vec = vec![]; + if let Some(type_info) = TypeInfo::from_declared_name( &self.declared_name, self.scope_id.unwrap(), @@ -53,24 +64,114 @@ impl TypeUse { class_context, ) { self.type_info = Some(type_info); - Ok(()) } else { - Err(vec![ - Diagnostic::new( - &format!("Unable to resolve symbol {}", self.declared_name), - self.declared_name_source_range.start(), - self.declared_name_source_range.end(), - ) - .with_reporter(file!(), line!()), - ]) + let diagnostic = Diagnostic::new( + &format!("Unable to resolve symbol {}", self.declared_name), + self.declared_name_source_range.start(), + self.declared_name_source_range.end(), + ) + .with_reporter(file!(), line!()); + diagnostics.push(diagnostic); } + + for generic_argument in &mut self.generic_arguments { + handle_diagnostics!( + generic_argument.check_name_usages(symbol_table, class_context), + diagnostics + ); + } + + diagnostics_result!(diagnostics) } pub fn type_check(&mut self, _symbol_table: &SymbolTable) -> Result<(), Vec> { - Ok(()) // no-op, for now + let mut diagnostics: Vec = vec![]; + + match self.type_info() { + TypeInfo::ClassInstance(class_symbol) => { + // check number of params/args match + let borrowed_class_symbol = class_symbol.borrow(); + let generic_parameters = borrowed_class_symbol.generic_parameters(); + if generic_parameters.len() != self.generic_arguments.len() { + let diagnostic = Diagnostic::new( + &format!( + "Expected {} generic arguments; found {}.", + generic_parameters.len(), + self.generic_arguments.len() + ), + self.declared_name_source_range.start(), + self.declared_name_source_range.end(), + ) + .with_reporter(file!(), line!()) + .with_error_code(INCORRECT_GENERIC_ARGUMENTS); + diagnostics.push(diagnostic); + } + + maybe_return_diagnostics!(diagnostics); + + // check that each arg is assignable to the param's extends + for i in 0..self.generic_arguments.len() { + let generic_parameter_symbol = generic_parameters[i].borrow(); + if generic_parameter_symbol.extends().len() > 0 { + unimplemented!("Generic extends not implemented yet.") + } + } + } + _ => { + // cannot extend a non-class type (except for Any) + if self.generic_arguments.len() > 0 { + let diagnostic = Diagnostic::new( + &format!( + "Type {} does not accept generic arguments.", + self.type_info() + ), + self.declared_name_source_range.start(), + self.declared_name_source_range.end(), + ) + .with_reporter(file!(), line!()) + .with_error_code(INCORRECT_GENERIC_ARGUMENTS); + diagnostics.push(diagnostic); + } + } + } + + // recurse on generic arguments + for generic_argument in &mut self.generic_arguments { + handle_diagnostics!(generic_argument.type_check(_symbol_table), diagnostics); + } + + diagnostics_result!(diagnostics) } pub fn type_info(&self) -> &TypeInfo { self.type_info.as_ref().unwrap() } } + +#[cfg(test)] +mod tests { + use crate::diagnostic::Diagnostic; + use crate::parser::parse_compilation_unit; + use crate::symbol_table::SymbolTable; + + #[test] + fn type_check_generics() -> Result<(), Vec> { + let mut compilation_unit = parse_compilation_unit( + " + class Foo + ctor(t: T) end + end + + fn useFoo(foo: Foo) end + ", + )?; + + let mut symbol_table = SymbolTable::new(); + + compilation_unit.gather_declared_names(&mut symbol_table)?; + compilation_unit.check_name_usages(&mut symbol_table)?; + compilation_unit.type_check(&mut symbol_table)?; + + Ok(()) + } +} diff --git a/dmc-lib/src/error_codes.rs b/dmc-lib/src/error_codes.rs index 260ac60..28c3ad2 100644 --- a/dmc-lib/src/error_codes.rs +++ b/dmc-lib/src/error_codes.rs @@ -1,6 +1,9 @@ pub type ErrorCode = usize; +pub const SYMBOL_ALREADY_DECLARED: ErrorCode = 14; pub const BINARY_INCOMPATIBLE_TYPES: ErrorCode = 15; pub const ASSIGN_MISMATCHED_TYPES: ErrorCode = 16; 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; diff --git a/dmc-lib/src/ir/ir_class.rs b/dmc-lib/src/ir/ir_class.rs index 3436f02..3c4b286 100644 --- a/dmc-lib/src/ir/ir_class.rs +++ b/dmc-lib/src/ir/ir_class.rs @@ -54,6 +54,7 @@ impl IrField { TypeInfo::ClassInstance(class_symbol) => VmTypeInfo::ClassInstance( fqn_parts_to_string(class_symbol.borrow().fqn_parts()).into(), ), + TypeInfo::GenericType(_) => VmTypeInfo::Any, _ => panic!(), }, ) diff --git a/dmc-lib/src/parser.rs b/dmc-lib/src/parser.rs index 8e0bbb2..a7d0ebd 100644 --- a/dmc-lib/src/parser.rs +++ b/dmc-lib/src/parser.rs @@ -10,6 +10,7 @@ use crate::ast::expression_statement::ExpressionStatement; use crate::ast::extern_function::ExternFunction; use crate::ast::field::Field; use crate::ast::function::Function; +use crate::ast::generic_parameter::GenericParameter; use crate::ast::identifier::Identifier; use crate::ast::integer_literal::IntegerLiteral; use crate::ast::let_statement::LetStatement; @@ -413,6 +414,7 @@ impl<'a> Parser<'a> { fn class(&mut self) -> Result> { self.expect_advance(TokenKind::Class)?; let identifier_token = self.expect_advance(TokenKind::Identifier)?; + let mut generic_parameters: Vec = vec![]; let mut fields = vec![]; let mut functions = vec![]; let mut maybe_constructor: Option = None; @@ -443,6 +445,14 @@ impl<'a> Parser<'a> { } Err(mut ctor_diagnostics) => diagnostics.append(&mut ctor_diagnostics), }, + TokenKind::Lt => match self.generic_parameters() { + Ok(parsed_params) => { + generic_parameters = parsed_params; + } + Err(mut generic_parameters_diagnostics) => { + diagnostics.append(&mut generic_parameters_diagnostics); + } + }, _ => unreachable!(), } } @@ -453,6 +463,7 @@ impl<'a> Parser<'a> { Ok(Class::new( self.token_text(&identifier_token), SourceRange::new(identifier_token.start(), identifier_token.end()), + generic_parameters, maybe_constructor, fields, functions, @@ -559,6 +570,42 @@ impl<'a> Parser<'a> { Ok(generic_arguments) } + fn generic_parameters(&mut self) -> Result, Vec> { + self.expect_advance(TokenKind::Lt)?; + let mut parameters: Vec = vec![]; + while self.current.is_some() && self.peek_current(TokenKind::Identifier) { + parameters.push(self.generic_parameter()?); + if self.current.is_some() && self.peek_current(TokenKind::Plus) { + self.advance(); // + + } else { + break; + } + } + self.expect_advance(TokenKind::Gt)?; + Ok(parameters) + } + + fn generic_parameter(&mut self) -> Result> { + let identifier = self.expect_advance(TokenKind::Identifier)?; + let mut extends_list: Vec = vec![]; + if self.current.is_some() && self.peek_current(TokenKind::Colon) { + self.advance(); // : + while self.current.is_some() && matches_type_use_first!(self.get_current().kind()) { + extends_list.push(self.type_use()?); + if self.current.is_some() && self.peek_current(TokenKind::Comma) { + self.advance(); // , + } else { + break; + } + } + } + Ok(GenericParameter::new( + self.token_text(&identifier), + SourceRange::new(identifier.start(), identifier.end()), + extends_list, + )) + } + fn public_class_member( &mut self, fields: &mut Vec, @@ -1233,6 +1280,11 @@ mod smoke_tests { fn nested_generic_args() { smoke_test("fn main(foo: Array>) end"); } + + #[test] + fn class_with_generic_param() { + smoke_test("class Foo end"); + } } #[cfg(test)] diff --git a/dmc-lib/src/scope/class_scope.rs b/dmc-lib/src/scope/class_scope.rs index 6052362..f83552c 100644 --- a/dmc-lib/src/scope/class_scope.rs +++ b/dmc-lib/src/scope/class_scope.rs @@ -2,6 +2,7 @@ use crate::symbol::class_symbol::ClassSymbol; use crate::symbol::constructor_symbol::ConstructorSymbol; use crate::symbol::field_symbol::FieldSymbol; use crate::symbol::function_symbol::FunctionSymbol; +use crate::symbol::generic_parameter_symbol::GenericParameterSymbol; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; @@ -9,6 +10,7 @@ use std::rc::Rc; pub struct ClassScope { debug_name: String, parent_id: usize, + generic_parameter_symbols: HashMap, Rc>>, class_symbols: HashMap, Rc>>, field_symbols: HashMap, Rc>>, function_symbols: HashMap, Rc>>, @@ -20,6 +22,7 @@ impl ClassScope { Self { debug_name: debug_name.into(), parent_id, + generic_parameter_symbols: HashMap::new(), class_symbols: HashMap::new(), field_symbols: HashMap::new(), function_symbols: HashMap::new(), @@ -27,6 +30,18 @@ impl ClassScope { } } + pub fn generic_parameter_symbols( + &self, + ) -> &HashMap, Rc>> { + &self.generic_parameter_symbols + } + + pub fn generic_parameter_symbols_mut( + &mut self, + ) -> &mut HashMap, Rc>> { + &mut self.generic_parameter_symbols + } + pub fn class_symbols(&self) -> &HashMap, Rc>> { &self.class_symbols } diff --git a/dmc-lib/src/symbol/class_symbol.rs b/dmc-lib/src/symbol/class_symbol.rs index 657e076..60ac2b4 100644 --- a/dmc-lib/src/symbol/class_symbol.rs +++ b/dmc-lib/src/symbol/class_symbol.rs @@ -3,6 +3,7 @@ use crate::symbol::Symbol; use crate::symbol::constructor_symbol::ConstructorSymbol; use crate::symbol::field_symbol::FieldSymbol; use crate::symbol::function_symbol::FunctionSymbol; +use crate::symbol::generic_parameter_symbol::GenericParameterSymbol; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; @@ -12,6 +13,7 @@ pub struct ClassSymbol { declared_name_source_range: SourceRange, fqn_parts: Vec>, is_extern: bool, + generic_parameters: Vec>>, constructor_symbol: Option>>, fields: HashMap, Rc>>, functions: HashMap, Rc>>, @@ -29,6 +31,7 @@ impl ClassSymbol { declared_name_source_range, fqn_parts, is_extern, + generic_parameters: vec![], constructor_symbol: None, fields: HashMap::new(), functions: HashMap::new(), @@ -39,6 +42,17 @@ impl ClassSymbol { &self.fqn_parts } + pub fn set_generic_parameters( + &mut self, + generic_parameters: Vec>>, + ) { + self.generic_parameters = generic_parameters; + } + + pub fn generic_parameters(&self) -> &[Rc>] { + &self.generic_parameters + } + pub fn set_constructor_symbol( &mut self, constructor_symbol: Option>>, @@ -46,6 +60,10 @@ impl ClassSymbol { self.constructor_symbol = constructor_symbol; } + pub fn set_fields(&mut self, fields: HashMap, Rc>>) { + self.fields = fields; + } + pub fn fields(&self) -> &HashMap, Rc>> { &self.fields } diff --git a/dmc-lib/src/symbol/generic_parameter_symbol.rs b/dmc-lib/src/symbol/generic_parameter_symbol.rs new file mode 100644 index 0000000..b5bcd56 --- /dev/null +++ b/dmc-lib/src/symbol/generic_parameter_symbol.rs @@ -0,0 +1,42 @@ +use crate::source_range::SourceRange; +use crate::symbol::Symbol; +use crate::type_info::TypeInfo; +use std::rc::Rc; + +pub struct GenericParameterSymbol { + declared_name: Rc, + declared_name_source_range: SourceRange, + extends: Option>, +} + +impl GenericParameterSymbol { + pub fn new(declared_name: &Rc, declared_name_source_range: &SourceRange) -> Self { + Self { + declared_name: declared_name.clone(), + declared_name_source_range: declared_name_source_range.clone(), + extends: None, + } + } + + pub fn set_extends(&mut self, extends: Vec) { + self.extends = Some(extends); + } + + pub fn extends(&self) -> &[TypeInfo] { + self.extends.as_ref().unwrap() + } +} + +impl Symbol for GenericParameterSymbol { + fn declared_name(&self) -> &str { + &self.declared_name + } + + fn declared_name_owned(&self) -> Rc { + self.declared_name.clone() + } + + fn declared_name_source_range(&self) -> &SourceRange { + &self.declared_name_source_range + } +} diff --git a/dmc-lib/src/symbol/mod.rs b/dmc-lib/src/symbol/mod.rs index cb8b1dc..4925e02 100644 --- a/dmc-lib/src/symbol/mod.rs +++ b/dmc-lib/src/symbol/mod.rs @@ -7,6 +7,7 @@ pub mod constructor_symbol; pub mod expressible_symbol; pub mod field_symbol; pub mod function_symbol; +pub mod generic_parameter_symbol; pub mod parameter_symbol; pub mod type_symbol; pub mod variable_symbol; diff --git a/dmc-lib/src/symbol/type_symbol.rs b/dmc-lib/src/symbol/type_symbol.rs index c150891..992dbb0 100644 --- a/dmc-lib/src/symbol/type_symbol.rs +++ b/dmc-lib/src/symbol/type_symbol.rs @@ -1,7 +1,9 @@ use crate::symbol::class_symbol::ClassSymbol; +use crate::symbol::generic_parameter_symbol::GenericParameterSymbol; use std::cell::RefCell; use std::rc::Rc; pub enum TypeSymbol { Class(Rc>), + GenericParameter(Rc>), } diff --git a/dmc-lib/src/symbol_table/helpers.rs b/dmc-lib/src/symbol_table/helpers.rs index 86acbbe..540173a 100644 --- a/dmc-lib/src/symbol_table/helpers.rs +++ b/dmc-lib/src/symbol_table/helpers.rs @@ -143,11 +143,19 @@ fn find_type_symbol_in_module(module_scope: &ModuleScope, name: &str) -> Option< .map(|symbol| TypeSymbol::Class(symbol.clone())) } -fn find_type_symbol_in_class(class_scope: &ClassScope, name: &str) -> Option { +pub fn find_type_symbol_in_class(class_scope: &ClassScope, name: &str) -> Option { class_scope .class_symbols() .get(name) .map(|symbol| TypeSymbol::Class(symbol.clone())) + .or_else(|| { + class_scope + .generic_parameter_symbols() + .get(name) + .map(|generic_parameter_symbol| { + TypeSymbol::GenericParameter(generic_parameter_symbol.clone()) + }) + }) } pub fn find_type_symbol(scope: &Scope, name: &str) -> Option { diff --git a/dmc-lib/src/symbol_table/mod.rs b/dmc-lib/src/symbol_table/mod.rs index 3ff6903..6ba70a6 100644 --- a/dmc-lib/src/symbol_table/mod.rs +++ b/dmc-lib/src/symbol_table/mod.rs @@ -11,12 +11,13 @@ use crate::symbol::constructor_symbol::ConstructorSymbol; use crate::symbol::expressible_symbol::ExpressibleSymbol; use crate::symbol::field_symbol::FieldSymbol; use crate::symbol::function_symbol::FunctionSymbol; +use crate::symbol::generic_parameter_symbol::GenericParameterSymbol; use crate::symbol::parameter_symbol::ParameterSymbol; use crate::symbol::type_symbol::TypeSymbol; use crate::symbol::variable_symbol::VariableSymbol; use crate::symbol_table::helpers::{ find_expressible_symbol, find_in_block_by_name, find_in_class_by_name, - find_in_function_by_name, find_in_module_by_name, find_type_symbol, + find_in_function_by_name, find_in_module_by_name, find_type_symbol, find_type_symbol_in_class, }; use std::cell::RefCell; use std::rc::Rc; @@ -124,6 +125,48 @@ impl SymbolTable { Ok(to_return) } + pub fn insert_generic_parameter_symbol( + &mut self, + generic_parameter_symbol: GenericParameterSymbol, + ) -> Result>, SymbolInsertError> { + let maybe_already_inserted = match self.current_scope() { + Scope::Class(class_scope) => { + find_type_symbol_in_class(class_scope, generic_parameter_symbol.declared_name()) + } + _ => panic!("Attempt to insert GenericParameterSymbol in incompatible scope"), + }; + + if let Some(already_inserted) = maybe_already_inserted { + match already_inserted { + TypeSymbol::Class(class_symbol) => { + return Err(SymbolInsertError::AlreadyDeclared(AlreadyDeclared::new( + class_symbol as Rc>, + ))); + } + TypeSymbol::GenericParameter(generic_parameter_symbol) => { + return Err(SymbolInsertError::AlreadyDeclared(AlreadyDeclared::new( + generic_parameter_symbol as Rc>, + ))); + } + } + } + + let name = generic_parameter_symbol.declared_name_owned(); + let as_rc = Rc::new(RefCell::new(generic_parameter_symbol)); + let to_return = as_rc.clone(); + + match self.current_scope_mut() { + Scope::Class(class_scope) => { + class_scope + .generic_parameter_symbols_mut() + .insert(name, as_rc); + } + _ => unreachable!(), + } + + Ok(to_return) + } + pub fn insert_constructor_symbol( &mut self, constructor_symbol: ConstructorSymbol, diff --git a/dmc-lib/src/type_info.rs b/dmc-lib/src/type_info.rs index c3ece06..52bc0fa 100644 --- a/dmc-lib/src/type_info.rs +++ b/dmc-lib/src/type_info.rs @@ -1,6 +1,7 @@ use crate::symbol::Symbol; use crate::symbol::class_symbol::ClassSymbol; use crate::symbol::function_symbol::FunctionSymbol; +use crate::symbol::generic_parameter_symbol::GenericParameterSymbol; use crate::symbol::type_symbol::TypeSymbol; use crate::symbol_table::SymbolTable; use std::cell::RefCell; @@ -15,6 +16,7 @@ pub enum TypeInfo { String, Function(Rc>), ClassInstance(Rc>), + GenericType(Rc>), Void, } @@ -35,6 +37,9 @@ impl Display for TypeInfo { TypeInfo::ClassInstance(class_symbol) => { write!(f, "{}", class_symbol.borrow().declared_name()) } + TypeInfo::GenericType(generic_parameter_symbol) => { + write!(f, "{}", generic_parameter_symbol.borrow().declared_name()) + } TypeInfo::Void => write!(f, "Void"), } } @@ -66,6 +71,9 @@ impl TypeInfo { None => None, Some(type_symbol) => match type_symbol { TypeSymbol::Class(class_symbol) => Some(TypeInfo::ClassInstance(class_symbol)), + TypeSymbol::GenericParameter(generic_parameter_symbol) => { + Some(TypeInfo::GenericType(generic_parameter_symbol)) + } }, }, } @@ -95,6 +103,14 @@ impl TypeInfo { _ => false, } } + TypeInfo::GenericType(generic_parameter_symbol) => { + if generic_parameter_symbol.borrow().extends().len() > 0 { + unimplemented!( + "Assigning to generic parameter type with extends type uses not yet supported." + ); + } + true + } TypeInfo::Void => { matches!(other, TypeInfo::Void) } diff --git a/dvm-lib/src/vm/type_info.rs b/dvm-lib/src/vm/type_info.rs index 41de60c..036c8b0 100644 --- a/dvm-lib/src/vm/type_info.rs +++ b/dvm-lib/src/vm/type_info.rs @@ -8,6 +8,7 @@ pub enum TypeInfo { Int, Double, String, + Any, } impl TypeInfo { @@ -36,6 +37,7 @@ impl TypeInfo { TypeInfo::String => { matches!(value, Value::String(_)) } + TypeInfo::Any => true, } } } diff --git a/e2e-tests/src/lib.rs b/e2e-tests/src/lib.rs index fecff9b..7a7d3c9 100644 --- a/e2e-tests/src/lib.rs +++ b/e2e-tests/src/lib.rs @@ -290,6 +290,31 @@ mod e2e_tests { assert_eq!(o.fields().len(), 1); assert_eq!(o.fields()[0].unwrap_int(), 42); } + + #[test] + fn see_what_happens() { + let context = prepare_context( + " + class Foo + mut t: T + ctor(_t: T) + t = _t + end + end + + fn main() -> Foo + Foo(42) + end + ", + ); + let result = get_result(&context, "main", &vec![]); + assert!(result.is_some()); + let value = result.unwrap(); + assert!(matches!(value, Value::Object(_))); + let o = value.unwrap_object().borrow(); + assert_eq!(o.fields().len(), 1); + assert_eq!(o.fields()[0].unwrap_int(), 42); + } } #[cfg(test)]