Add ctors, most name-analysis for classes/fields/constructors.

This commit is contained in:
Jesse Brault 2026-03-12 12:35:36 -05:00
parent 75dcca0002
commit ad821ce6a7
15 changed files with 462 additions and 35 deletions

View File

@ -1,3 +1,4 @@
use crate::ast::constructor::Constructor;
use crate::ast::field::Field;
use crate::ast::function::Function;
use crate::diagnostic::Diagnostic;
@ -9,6 +10,7 @@ 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>,
}
@ -17,12 +19,14 @@ 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,
}
@ -73,7 +77,12 @@ impl Class {
return Err(fields_diagnostics);
}
// 4. gather functions
// 4. gather constructor
if let Some(constructor) = &mut self.constructor {
constructor.gather_declared_names(symbol_table)?;
}
// 5. gather functions
let functions_diagnostics: Vec<Diagnostic> = self
.functions
.iter_mut()
@ -86,13 +95,18 @@ impl Class {
return Err(functions_diagnostics);
}
// 5. pop scope
// 6. pop scope
symbol_table.pop_scope();
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))
.transpose()?;
let fields_diagnostics: Vec<Diagnostic> = self
.fields
.iter_mut()
@ -104,6 +118,10 @@ impl Class {
return Err(fields_diagnostics);
}
if let Some(constructor) = &mut self.constructor {
constructor.check_name_usages(symbol_table)?;
}
let functions_diagnostics: Vec<Diagnostic> = self
.functions
.iter_mut()
@ -120,7 +138,26 @@ impl Class {
}
pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec<Diagnostic>> {
todo!()
// 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 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));
// todo check constructor and functions and re-check fields
if diagnostics.is_empty() {
Ok(())
} else {
Err(diagnostics)
}
}
}
@ -134,7 +171,10 @@ mod tests {
let parse_result = parse_compilation_unit(
"
class Foo
mut bar = 42
mut bar: Int
ctor(_bar: Int)
end
fn baz() -> Int
bar
@ -143,7 +183,7 @@ mod tests {
class Qux
fn foo() -> Foo
Foo()
Foo(42)
end
end
",

View File

@ -0,0 +1,127 @@
use crate::ast::parameter::Parameter;
use crate::ast::statement::Statement;
use crate::diagnostic::Diagnostic;
use crate::source_range::SourceRange;
use crate::symbol::constructor_symbol::ConstructorSymbol;
use crate::symbol::parameter_symbol::ParameterSymbol;
use crate::symbol_table::{SymbolInsertError, SymbolTable};
use std::cell::RefCell;
use std::rc::Rc;
pub struct Constructor {
is_public: bool,
ctor_keyword_source_range: SourceRange,
parameters: Vec<Parameter>,
statements: Vec<Statement>,
}
impl Constructor {
pub fn new(
is_public: bool,
ctor_keyword_source_range: SourceRange,
parameters: Vec<Parameter>,
statements: Vec<Statement>,
) -> Self {
Self {
is_public,
ctor_keyword_source_range,
parameters,
statements,
}
}
pub fn gather_declared_names(
&mut self,
symbol_table: &mut SymbolTable,
) -> Result<(), Vec<Diagnostic>> {
// insert constructor symbol
let to_insert = ConstructorSymbol::new(self.ctor_keyword_source_range.clone(), false);
let constructor_symbol =
symbol_table
.insert_constructor_symbol(to_insert)
.map_err(|symbol_insert_error| match symbol_insert_error {
SymbolInsertError::AlreadyDeclared(_) => {
vec![
Diagnostic::new(
"Cannot declare more than one constructor.",
self.ctor_keyword_source_range.start(),
self.ctor_keyword_source_range.end(),
)
.with_reporter(file!(), line!()),
]
}
})?;
symbol_table.push_function_scope("ctor_scope");
let mut parameter_symbols: Vec<Rc<RefCell<ParameterSymbol>>> = vec![];
let mut parameters_diagnostics = vec![];
for parameter in &mut self.parameters {
match parameter.gather_declared_names(symbol_table) {
Ok(parameter_symbol) => {
parameter_symbols.push(parameter_symbol);
}
Err(mut ds) => {
parameters_diagnostics.append(&mut ds);
}
}
}
if !parameters_diagnostics.is_empty() {
symbol_table.pop_scope();
return Err(parameters_diagnostics);
} else {
constructor_symbol
.borrow_mut()
.set_parameters(parameter_symbols);
}
symbol_table.push_block_scope("ctor_main_block");
let statements_diagnostics = self
.statements
.iter_mut()
.map(|stmt| stmt.gather_declared_names(symbol_table))
.filter_map(Result::err)
.flatten()
.collect::<Vec<_>>();
symbol_table.pop_scope(); // block
symbol_table.pop_scope(); // function
if statements_diagnostics.is_empty() {
Ok(())
} else {
Err(statements_diagnostics)
}
}
pub fn check_name_usages(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec<Diagnostic>> {
let parameters_diagnostics: Vec<Diagnostic> = self
.parameters
.iter_mut()
.map(|param| param.check_name_usages(symbol_table))
.filter_map(Result::err)
.flatten()
.collect();
if !parameters_diagnostics.is_empty() {
return Err(parameters_diagnostics);
}
let statements_diagnostics: Vec<Diagnostic> = self
.statements
.iter_mut()
.map(|statement| statement.check_name_usages(symbol_table))
.filter_map(Result::err)
.flatten()
.collect();
if statements_diagnostics.is_empty() {
Ok(())
} else {
Err(statements_diagnostics)
}
}
}

View File

@ -4,6 +4,7 @@ use crate::diagnostic::Diagnostic;
use crate::source_range::SourceRange;
use crate::symbol::field_symbol::FieldSymbol;
use crate::symbol_table::{SymbolInsertError, SymbolTable};
use std::cell::RefCell;
use std::rc::Rc;
pub struct Field {
@ -13,6 +14,7 @@ pub struct Field {
is_mut: bool,
declared_type: Option<Box<TypeUse>>,
initializer: Option<Box<Expression>>,
field_symbol: Option<Rc<RefCell<FieldSymbol>>>,
}
impl Field {
@ -31,6 +33,7 @@ impl Field {
is_mut,
declared_type: declared_type.map(Box::new),
initializer: initializer.map(Box::new),
field_symbol: None,
}
}
@ -42,7 +45,7 @@ impl Field {
let to_insert =
FieldSymbol::new(&self.declared_name, self.declared_name_source_range.clone());
symbol_table
let field_symbol = symbol_table
.insert_field_symbol(to_insert)
.map_err(|e| match e {
SymbolInsertError::AlreadyDeclared(already_declared) => {
@ -57,7 +60,14 @@ impl Field {
}
})?;
// 2. gather initializer, if present
// save for later
self.field_symbol = Some(field_symbol);
// 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)?;
}
@ -80,4 +90,83 @@ impl Field {
}
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.to_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.to_type_info()
}
}
}
None => {
// type is the initializer
match self.initializer.as_ref() {
Some(initializer) => initializer.type_info().clone(),
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);
if diagnostics.is_empty() {
Ok(())
} else {
Err(diagnostics)
}
}
}

View File

@ -169,9 +169,11 @@ impl Function {
}
} else {
// we don't have a given return type, so it's void
self.function_symbol.as_mut().unwrap().borrow_mut().set_return_type_info(
TypeInfo::Void
);
self.function_symbol
.as_mut()
.unwrap()
.borrow_mut()
.set_return_type_info(TypeInfo::Void);
}
// statements

View File

@ -2,6 +2,7 @@ pub mod add_expression;
pub mod call;
pub mod class;
pub mod compilation_unit;
pub mod constructor;
pub mod double_literal;
pub mod expression;
pub mod expression_statement;

View File

@ -56,6 +56,10 @@ impl TypeUse {
}
}
pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec<Diagnostic>> {
Ok(()) // no-op, for now
}
pub fn to_type_info(&self) -> TypeInfo {
match self.type_symbol.as_ref().unwrap() {
TypeSymbol::Class(class_symbol) => TypeInfo::Class(class_symbol.clone()),

View File

@ -136,6 +136,7 @@ impl<'a> Lexer<'a> {
"self" => TokenKind::SelfKw,
"pub" => TokenKind::Public,
"mut" => TokenKind::Mut,
"ctor" => TokenKind::Ctor,
_ => TokenKind::Identifier,
};
Token::new(self.position, self.position + prefix.len(), token_kind)

View File

@ -2,6 +2,7 @@ use crate::ast::add_expression::AddExpression;
use crate::ast::call::Call;
use crate::ast::class::Class;
use crate::ast::compilation_unit::CompilationUnit;
use crate::ast::constructor::Constructor;
use crate::ast::double_literal::DoubleLiteral;
use crate::ast::expression::Expression;
use crate::ast::expression_statement::ExpressionStatement;
@ -49,6 +50,12 @@ macro_rules! matches_expression_first {
};
}
macro_rules! matches_statement_first {
( $token_kind : expr ) => {
matches!($token_kind, TokenKind::Let) || matches_expression_first!($token_kind)
};
}
struct Parser<'a> {
input: &'a str,
lexer: Lexer<'a>,
@ -382,12 +389,17 @@ impl<'a> Parser<'a> {
let identifier_token = self.expect_advance(TokenKind::Identifier)?;
let mut fields = vec![];
let mut functions = vec![];
let mut maybe_constructor: Option<Constructor> = None;
let mut diagnostics = vec![];
while self.current.is_some() && !self.peek_current(TokenKind::End) {
match self.get_current().kind() {
TokenKind::Public => match self.public_class_member(&mut fields, &mut functions) {
TokenKind::Public => match self.public_class_member(
&mut fields,
&mut functions,
&mut maybe_constructor,
) {
Ok(_) => {}
Err(mut member_diagnostics) => diagnostics.append(&mut member_diagnostics),
},
@ -399,6 +411,12 @@ impl<'a> Parser<'a> {
Ok(function) => functions.push(function),
Err(mut function_diagnostics) => diagnostics.append(&mut function_diagnostics),
},
TokenKind::Ctor => match self.constructor() {
Ok(constructor) => {
maybe_constructor = Some(constructor);
}
Err(mut ctor_diagnostics) => diagnostics.append(&mut ctor_diagnostics),
},
_ => unreachable!(),
}
}
@ -409,6 +427,7 @@ impl<'a> Parser<'a> {
Ok(Class::new(
self.token_text(&identifier_token),
SourceRange::new(identifier_token.start(), identifier_token.end()),
maybe_constructor,
fields,
functions,
))
@ -469,26 +488,29 @@ impl<'a> Parser<'a> {
&mut self,
fields: &mut Vec<Field>,
functions: &mut Vec<Function>,
maybe_ctor: &mut Option<Constructor>,
) -> Result<(), Vec<Diagnostic>> {
if self.lookahead.is_some() {
if matches!(
self.lookahead.as_ref().unwrap().kind(),
TokenKind::Mut | TokenKind::Identifier
) {
fields.push(self.field()?);
} else if matches!(self.lookahead.as_ref().unwrap().kind(), TokenKind::Fn) {
functions.push(self.function()?);
} else {
let lookahead = self.lookahead.as_ref().unwrap();
return Err(vec![Diagnostic::new(
&format!(
"Expected any of {:?}; found {:?}",
[TokenKind::Mut, TokenKind::Identifier, TokenKind::Fn],
lookahead.kind()
),
lookahead.start(),
lookahead.end(),
)]);
match self.lookahead.as_ref().unwrap().kind() {
TokenKind::Mut | TokenKind::Identifier => {
fields.push(self.field()?);
}
TokenKind::Fn => functions.push(self.function()?),
TokenKind::Ctor => {
maybe_ctor.replace(self.constructor()?);
}
_ => {
let lookahead = self.lookahead.as_ref().unwrap();
return Err(vec![Diagnostic::new(
&format!(
"Expected any of {:?}; found {:?}",
[TokenKind::Mut, TokenKind::Identifier, TokenKind::Fn],
lookahead.kind()
),
lookahead.start(),
lookahead.end(),
)]);
}
}
Ok(())
} else {
@ -504,6 +526,56 @@ impl<'a> Parser<'a> {
}
}
fn constructor(&mut self) -> Result<Constructor, Vec<Diagnostic>> {
let is_public = if self.current.is_some() && self.peek_current(TokenKind::Public) {
self.advance();
true
} else {
false
};
let ctor_keyword = self.expect_advance(TokenKind::Ctor)?;
self.expect_advance(TokenKind::LeftParentheses)?;
let parameters = if self.current.is_some() && self.peek_current(TokenKind::Identifier) {
self.parameter_list()?
} else {
vec![]
};
self.expect_advance(TokenKind::RightParentheses)?;
// statements
let mut statements: Vec<Statement> = vec![];
let mut diagnostics: Vec<Diagnostic> = vec![];
while self.current.is_some()
&& matches_statement_first!(self.current.as_ref().unwrap().kind())
{
match self.statement() {
Ok(statement) => {
statements.push(statement);
}
Err(mut statement_diagnostics) => {
diagnostics.append(&mut statement_diagnostics);
}
}
}
self.expect_advance(TokenKind::End)?;
if diagnostics.is_empty() {
Ok(Constructor::new(
is_public,
SourceRange::new(ctor_keyword.start(), ctor_keyword.end()),
parameters,
statements,
))
} else {
Err(diagnostics)
}
}
fn field(&mut self) -> Result<Field, Vec<Diagnostic>> {
let is_public = if self.current.is_some() && self.peek_current(TokenKind::Public) {
self.advance();

View File

@ -1,4 +1,5 @@
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 std::cell::RefCell;
@ -11,6 +12,7 @@ pub struct ClassScope {
class_symbols: HashMap<Rc<str>, Rc<RefCell<ClassSymbol>>>,
field_symbols: HashMap<Rc<str>, Rc<RefCell<FieldSymbol>>>,
function_symbols: HashMap<Rc<str>, Rc<RefCell<FunctionSymbol>>>,
constructor_symbol: Option<Rc<RefCell<ConstructorSymbol>>>,
}
impl ClassScope {
@ -21,6 +23,7 @@ impl ClassScope {
class_symbols: HashMap::new(),
field_symbols: HashMap::new(),
function_symbols: HashMap::new(),
constructor_symbol: None,
}
}
@ -48,6 +51,14 @@ impl ClassScope {
&mut self.function_symbols
}
pub fn constructor_symbol(&self) -> Option<&Rc<RefCell<ConstructorSymbol>>> {
self.constructor_symbol.as_ref()
}
pub fn constructor_symbol_mut(&mut self) -> &mut Option<Rc<RefCell<ConstructorSymbol>>> {
&mut self.constructor_symbol
}
pub fn parent_id(&self) -> usize {
self.parent_id
}

View File

@ -0,0 +1,47 @@
use crate::source_range::SourceRange;
use crate::symbol::Symbol;
use crate::symbol::parameter_symbol::ParameterSymbol;
use std::cell::RefCell;
use std::rc::Rc;
pub struct ConstructorSymbol {
ctor_keyword_source_range: SourceRange,
is_extern: bool,
parameters: Option<Vec<Rc<RefCell<ParameterSymbol>>>>,
}
impl ConstructorSymbol {
pub fn new(ctor_keyword_source_range: SourceRange, is_extern: bool) -> Self {
Self {
ctor_keyword_source_range,
is_extern,
parameters: None,
}
}
pub fn set_parameters(&mut self, parameters: Vec<Rc<RefCell<ParameterSymbol>>>) {
self.parameters = Some(parameters);
}
pub fn parameters(&self) -> &[Rc<RefCell<ParameterSymbol>>] {
self.parameters.as_ref().unwrap()
}
pub fn is_extern(&self) -> bool {
self.is_extern
}
}
impl Symbol for ConstructorSymbol {
fn declared_name(&self) -> &str {
"ctor"
}
fn declared_name_owned(&self) -> Rc<str> {
Rc::from(self.declared_name())
}
fn declared_name_source_range(&self) -> &SourceRange {
&self.ctor_keyword_source_range
}
}

View File

@ -19,12 +19,8 @@ pub enum ExpressibleSymbol {
impl ExpressibleSymbol {
pub fn type_info(&self) -> TypeInfo {
match self {
ExpressibleSymbol::Class(class_symbol) => {
todo!()
}
ExpressibleSymbol::Field(field_symbol) => {
todo!()
}
ExpressibleSymbol::Class(class_symbol) => TypeInfo::Class(class_symbol.clone()),
ExpressibleSymbol::Field(field_symbol) => field_symbol.borrow().type_info().clone(),
ExpressibleSymbol::Function(function_symbol) => {
TypeInfo::Function(function_symbol.clone()) // n.b. not the return type!
}

View File

@ -17,6 +17,14 @@ impl FieldSymbol {
type_info: None,
}
}
pub fn set_type_info(&mut self, type_info: TypeInfo) {
self.type_info = Some(type_info);
}
pub fn type_info(&self) -> &TypeInfo {
self.type_info.as_ref().unwrap()
}
}
impl Symbol for FieldSymbol {

View File

@ -2,6 +2,7 @@ use crate::source_range::SourceRange;
use std::rc::Rc;
pub mod class_symbol;
pub mod constructor_symbol;
pub mod expressible_symbol;
pub mod field_symbol;
pub mod function_symbol;

View File

@ -7,6 +7,7 @@ use crate::scope::function_scope::FunctionScope;
use crate::scope::module_scope::ModuleScope;
use crate::symbol::Symbol;
use crate::symbol::class_symbol::ClassSymbol;
use crate::symbol::constructor_symbol::ConstructorSymbol;
use crate::symbol::expressible_symbol::ExpressibleSymbol;
use crate::symbol::field_symbol::FieldSymbol;
use crate::symbol::function_symbol::FunctionSymbol;
@ -123,6 +124,32 @@ impl SymbolTable {
Ok(to_return)
}
pub fn insert_constructor_symbol(
&mut self,
constructor_symbol: ConstructorSymbol,
) -> Result<Rc<RefCell<ConstructorSymbol>>, SymbolInsertError> {
let maybe_already_inserted = match self.current_scope() {
Scope::Class(class_scope) => class_scope.constructor_symbol(),
_ => panic!("Attempt to insert ConstructorSymbol in incompatible scope"),
};
if let Some(already_inserted) = maybe_already_inserted {
return Err(SymbolInsertError::AlreadyDeclared(AlreadyDeclared::new(
already_inserted.clone() as Rc<RefCell<dyn Symbol>>,
)));
}
let as_rc = Rc::new(RefCell::new(constructor_symbol));
let to_return = as_rc.clone();
match self.current_scope_mut() {
Scope::Class(class_scope) => {
class_scope.constructor_symbol_mut().replace(as_rc);
}
_ => unreachable!(),
}
Ok(to_return)
}
pub fn insert_field_symbol(
&mut self,
field_symbol: FieldSymbol,

View File

@ -47,4 +47,5 @@ pub enum TokenKind {
SelfKw,
Public,
Mut,
Ctor,
}