Name analysis for classes and affected things.

This commit is contained in:
Jesse Brault 2026-03-11 22:15:23 -05:00
parent efecd6b9c8
commit 75dcca0002
13 changed files with 486 additions and 44 deletions

View File

@ -133,8 +133,12 @@ fn compile_expression(
"__repl",
SourceRange::new(0, 0),
false,
expression.type_info().clone(), // dubious
)));
fake_function_symbol
.borrow_mut()
.set_return_type_info(expression.type_info().clone());
let mut ir_function = IrFunction::new(
fake_function_symbol,
&[],

View File

@ -2,7 +2,8 @@ use crate::ast::field::Field;
use crate::ast::function::Function;
use crate::diagnostic::Diagnostic;
use crate::source_range::SourceRange;
use crate::symbol_table::SymbolTable;
use crate::symbol::class_symbol::ClassSymbol;
use crate::symbol_table::{SymbolInsertError, SymbolTable};
use std::rc::Rc;
pub struct Class {
@ -32,16 +33,142 @@ impl Class {
symbol_table: &mut SymbolTable,
) -> Result<(), Vec<Diagnostic>> {
// 1. insert class symbol
// 2. gather fields
// 3. gather functions
todo!()
let to_insert = ClassSymbol::new(
&self.declared_name,
self.declared_name_source_range.clone(),
false,
);
symbol_table
.insert_class_symbol(to_insert)
.map_err(|e| match e {
SymbolInsertError::AlreadyDeclared(already_declared) => {
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_reporter(file!(), line!()),
]
}
})?;
// 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()
.map(|field| field.gather_declared_names(symbol_table))
.filter_map(Result::err)
.flatten()
.collect();
if !fields_diagnostics.is_empty() {
return Err(fields_diagnostics);
}
// 4. gather functions
let functions_diagnostics: Vec<Diagnostic> = self
.functions
.iter_mut()
.map(|function| function.gather_declared_names(symbol_table))
.filter_map(Result::err)
.flatten()
.collect();
if !functions_diagnostics.is_empty() {
return Err(functions_diagnostics);
}
// 5. pop scope
symbol_table.pop_scope();
Ok(())
}
pub fn check_name_usages(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec<Diagnostic>> {
todo!()
let fields_diagnostics: Vec<Diagnostic> = self
.fields
.iter_mut()
.map(|field| field.check_name_usages(symbol_table))
.filter_map(Result::err)
.flatten()
.collect();
if !fields_diagnostics.is_empty() {
return Err(fields_diagnostics);
}
let functions_diagnostics: Vec<Diagnostic> = self
.functions
.iter_mut()
.map(|function| function.check_name_usages(symbol_table))
.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>> {
todo!()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::parse_compilation_unit;
#[test]
fn name_analysis_no_diagnostics() {
let parse_result = parse_compilation_unit(
"
class Foo
mut bar = 42
fn baz() -> Int
bar
end
end
class Qux
fn foo() -> Foo
Foo()
end
end
",
);
let mut compilation_unit = match parse_result {
Ok(compilation_unit) => compilation_unit,
Err(diagnostics) => {
panic!("{:?}", diagnostics);
}
};
let mut symbol_table = SymbolTable::new();
match compilation_unit.gather_declared_names(&mut symbol_table) {
Ok(_) => {}
Err(diagnostics) => {
panic!("{:?}", diagnostics);
}
}
match compilation_unit.check_name_usages(&symbol_table) {
Ok(_) => {}
Err(diagnostics) => {
panic!("{:?}", diagnostics);
}
}
}
}

View File

@ -4,12 +4,15 @@ use crate::diagnostic::Diagnostic;
use crate::source_range::SourceRange;
use crate::symbol::function_symbol::FunctionSymbol;
use crate::symbol_table::{SymbolInsertError, SymbolTable};
use std::cell::RefCell;
use std::rc::Rc;
pub struct ExternFunction {
declared_name: String,
declared_name_source_range: SourceRange,
parameters: Vec<Parameter>,
return_type: TypeUse,
function_symbol: Option<Rc<RefCell<FunctionSymbol>>>,
}
impl ExternFunction {
@ -24,6 +27,7 @@ impl ExternFunction {
declared_name_source_range,
parameters,
return_type,
function_symbol: None,
}
}
@ -41,7 +45,6 @@ impl ExternFunction {
&self.declared_name,
self.declared_name_source_range.clone(),
true,
self.return_type.to_type_info(),
));
let function_symbol = match insert_result {
@ -82,6 +85,16 @@ impl ExternFunction {
.borrow_mut()
.set_parameters(parameter_symbols);
self.function_symbol = Some(function_symbol);
// handle return type
match self.return_type.gather_declared_names(symbol_table) {
Ok(_) => {}
Err(mut type_use_diagnostics) => {
diagnostics.append(&mut type_use_diagnostics);
}
}
symbol_table.pop_scope(); // function scope
if diagnostics.is_empty() {
@ -92,14 +105,29 @@ impl ExternFunction {
}
pub fn check_name_usages(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec<Diagnostic>> {
let diagnostics: Vec<Diagnostic> = self
let mut diagnostics: Vec<Diagnostic> = self
.parameters
.iter_mut()
.map(|parameter| parameter.check_name_usages(symbol_table))
.filter_map(Result::err)
.flatten()
.collect();
match self.return_type.check_name_usages(symbol_table) {
Ok(_) => {}
Err(mut return_type_diagnostics) => {
diagnostics.append(&mut return_type_diagnostics);
}
}
if diagnostics.is_empty() {
// set return type info on symbol now that its available
self.function_symbol
.as_mut()
.unwrap()
.borrow_mut()
.set_return_type_info(self.return_type.to_type_info());
Ok(())
} else {
Err(diagnostics)

View File

@ -1,6 +1,9 @@
use crate::ast::expression::Expression;
use crate::ast::type_use::TypeUse;
use crate::diagnostic::Diagnostic;
use crate::source_range::SourceRange;
use crate::symbol::field_symbol::FieldSymbol;
use crate::symbol_table::{SymbolInsertError, SymbolTable};
use std::rc::Rc;
pub struct Field {
@ -30,4 +33,51 @@ impl Field {
initializer: initializer.map(Box::new),
}
}
pub fn gather_declared_names(
&mut self,
symbol_table: &mut SymbolTable,
) -> Result<(), Vec<Diagnostic>> {
// 1. insert field symbol
let to_insert =
FieldSymbol::new(&self.declared_name, self.declared_name_source_range.clone());
symbol_table
.insert_field_symbol(to_insert)
.map_err(|e| match e {
SymbolInsertError::AlreadyDeclared(already_declared) => {
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(),
)]
}
})?;
// 2. gather initializer, if present
if let Some(initializer) = &mut self.initializer {
initializer.gather_declared_names(symbol_table)?;
}
Ok(())
}
pub fn check_name_usages(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec<Diagnostic>> {
if let Some(type_use) = &mut self.declared_type {
type_use.check_name_usages(symbol_table)?;
}
// This is going to get hairy, because users might attempt to use a field in an initializer
// (for either this field, or another one) before it's actually initialized. As such, we
// need a way to prevent lookup of current class' fields in the initializer.
// For now, the following is okay so long as we don't start referencing things in the
// initializers.
if let Some(initializer) = self.initializer.as_mut() {
initializer.check_name_usages(symbol_table)?;
}
Ok(())
}
}

View File

@ -58,15 +58,15 @@ impl Function {
) -> Result<(), Vec<Diagnostic>> {
let mut diagnostics = vec![];
if !diagnostics.is_empty() {
return Err(diagnostics);
}
// insert function symbol
let insert_result = symbol_table.insert_function_symbol(FunctionSymbol::new(
self.declared_name(),
self.declared_name_source_range.clone(),
false,
self.return_type
.as_ref()
.map(|return_type| return_type.to_type_info())
.unwrap_or(TypeInfo::Void),
));
// get function symbol if successful
@ -109,6 +109,16 @@ impl Function {
.borrow_mut()
.set_parameters(parameter_symbols);
// return type
if let Some(type_use) = &mut self.return_type {
match type_use.gather_declared_names(symbol_table) {
Ok(_) => {}
Err(mut type_use_diagnostics) => {
diagnostics.append(&mut type_use_diagnostics);
}
}
}
symbol_table.push_block_scope(&format!("main_block_scope({})", self.declared_name));
for statement in &mut self.statements {
match statement.gather_declared_names(symbol_table) {
@ -142,6 +152,28 @@ impl Function {
.collect(),
);
// return type
if let Some(type_use) = &mut self.return_type {
match type_use.check_name_usages(symbol_table) {
Ok(_) => {
// set return type info on function symbol
self.function_symbol
.as_mut()
.unwrap()
.borrow_mut()
.set_return_type_info(type_use.to_type_info());
}
Err(mut type_use_diagnostics) => {
diagnostics.append(&mut type_use_diagnostics);
}
}
} 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
);
}
// statements
for statement in &mut self.statements {
match statement.check_name_usages(symbol_table) {

View File

@ -32,6 +32,7 @@ impl Parameter {
&mut self,
symbol_table: &mut SymbolTable,
) -> Result<Rc<RefCell<ParameterSymbol>>, Vec<Diagnostic>> {
// insert parameter symbol
let insert_result = symbol_table.insert_parameter_symbol(ParameterSymbol::new(
&self.declared_name,
self.declared_name_source_range.clone(),
@ -40,26 +41,32 @@ impl Parameter {
match insert_result {
Ok(parameter_symbol) => {
self.parameter_symbol = Some(parameter_symbol.clone());
Ok(parameter_symbol)
}
Err(symbol_insert_error) => match symbol_insert_error {
SymbolInsertError::AlreadyDeclared(already_declared) => Err(vec![Diagnostic::new(
&format!(
"Parameter {} already declared.",
already_declared.symbol().borrow().declared_name()
),
self.declared_name_source_range.start(),
self.declared_name_source_range.end(),
)]),
},
Err(symbol_insert_error) => {
return match symbol_insert_error {
SymbolInsertError::AlreadyDeclared(already_declared) => {
Err(vec![Diagnostic::new(
&format!(
"Parameter {} already declared.",
already_declared.symbol().borrow().declared_name()
),
self.declared_name_source_range.start(),
self.declared_name_source_range.end(),
)])
}
};
}
}
// type use
self.type_use.gather_declared_names(symbol_table)?;
Ok(self.parameter_symbol.clone().unwrap())
}
pub fn check_name_usages(
&mut self,
_symbol_table: &SymbolTable,
) -> Result<(), Vec<Diagnostic>> {
Ok(()) // no-op for now
pub fn check_name_usages(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec<Diagnostic>> {
self.type_use.check_name_usages(symbol_table)?;
Ok(())
}
pub fn type_check(&mut self, _symbol_table: &SymbolTable) -> Result<(), Vec<Diagnostic>> {

View File

@ -1,9 +1,14 @@
use crate::diagnostic::Diagnostic;
use crate::source_range::SourceRange;
use crate::symbol::type_symbol::{PrimitiveTypeSymbol, TypeSymbol};
use crate::symbol_table::SymbolTable;
use crate::type_info::TypeInfo;
pub struct TypeUse {
declared_name: String,
declared_name_source_range: SourceRange,
scope_id: Option<usize>,
type_symbol: Option<TypeSymbol>,
}
impl TypeUse {
@ -11,6 +16,8 @@ impl TypeUse {
Self {
declared_name: declared_name.into(),
declared_name_source_range,
scope_id: None,
type_symbol: None,
}
}
@ -18,7 +25,47 @@ impl TypeUse {
&self.declared_name
}
pub fn gather_declared_names(
&mut self,
symbol_table: &mut SymbolTable,
) -> Result<(), Vec<Diagnostic>> {
self.scope_id = Some(symbol_table.current_scope_id());
Ok(())
}
pub fn check_name_usages(&mut self, symbol_table: &SymbolTable) -> Result<(), Vec<Diagnostic>> {
let maybe_type_symbol =
symbol_table.find_type_symbol(self.scope_id.unwrap(), &self.declared_name);
if let Some(type_symbol) = maybe_type_symbol {
self.type_symbol = Some(type_symbol);
Ok(())
} else if let Some(primitive_type_symbol) =
PrimitiveTypeSymbol::try_from_declared_name(self.declared_name())
{
self.type_symbol = Some(TypeSymbol::Primitive(primitive_type_symbol));
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!()),
])
}
}
pub fn to_type_info(&self) -> TypeInfo {
TypeInfo::from_declared_name(self.declared_name())
match self.type_symbol.as_ref().unwrap() {
TypeSymbol::Class(class_symbol) => TypeInfo::Class(class_symbol.clone()),
TypeSymbol::Primitive(primitive_type) => match primitive_type {
PrimitiveTypeSymbol::Any => TypeInfo::Any,
PrimitiveTypeSymbol::Int => TypeInfo::Integer,
PrimitiveTypeSymbol::Double => TypeInfo::Double,
PrimitiveTypeSymbol::String => TypeInfo::String,
PrimitiveTypeSymbol::Void => TypeInfo::Void,
},
}
}
}

View File

@ -10,7 +10,7 @@ pub struct FunctionSymbol {
declared_name_source_range: SourceRange,
is_extern: bool,
parameters: Option<Vec<Rc<RefCell<ParameterSymbol>>>>,
return_type: TypeInfo,
return_type: Option<TypeInfo>,
}
impl FunctionSymbol {
@ -18,27 +18,16 @@ impl FunctionSymbol {
declared_name: &str,
declared_name_source_range: SourceRange,
is_extern: bool,
return_type: TypeInfo,
) -> Self {
Self {
declared_name: declared_name.into(),
declared_name_source_range,
is_extern,
parameters: None,
return_type,
return_type: None,
}
}
#[deprecated(note = "use declared_name")]
pub fn name(&self) -> &str {
&self.declared_name
}
#[deprecated(note = "use declared_name_owned")]
pub fn name_owned(&self) -> Rc<str> {
self.declared_name.clone()
}
pub fn set_parameters(&mut self, parameters: Vec<Rc<RefCell<ParameterSymbol>>>) {
self.parameters = Some(parameters);
}
@ -47,8 +36,12 @@ impl FunctionSymbol {
self.parameters.as_ref().unwrap()
}
pub fn set_return_type_info(&mut self, return_type: TypeInfo) {
self.return_type = Some(return_type);
}
pub fn return_type_info(&self) -> &TypeInfo {
&self.return_type
self.return_type.as_ref().unwrap()
}
pub fn is_extern(&self) -> bool {

View File

@ -6,6 +6,7 @@ pub mod expressible_symbol;
pub mod field_symbol;
pub mod function_symbol;
pub mod parameter_symbol;
pub mod type_symbol;
pub mod variable_symbol;
pub trait Symbol {

View File

@ -0,0 +1,29 @@
use crate::symbol::class_symbol::ClassSymbol;
use std::cell::RefCell;
use std::rc::Rc;
pub enum TypeSymbol {
Class(Rc<RefCell<ClassSymbol>>),
Primitive(PrimitiveTypeSymbol),
}
pub enum PrimitiveTypeSymbol {
Any,
Int,
Double,
String,
Void,
}
impl PrimitiveTypeSymbol {
pub fn try_from_declared_name(declared_name: &str) -> Option<PrimitiveTypeSymbol> {
match declared_name {
"Any" => Some(PrimitiveTypeSymbol::Any),
"Int" => Some(PrimitiveTypeSymbol::Int),
"Double" => Some(PrimitiveTypeSymbol::Double),
"String" => Some(PrimitiveTypeSymbol::String),
"Void" => Some(PrimitiveTypeSymbol::Void),
_ => None,
}
}
}

View File

@ -5,6 +5,7 @@ use crate::scope::function_scope::FunctionScope;
use crate::scope::module_scope::ModuleScope;
use crate::symbol::Symbol;
use crate::symbol::expressible_symbol::ExpressibleSymbol;
use crate::symbol::type_symbol::TypeSymbol;
use std::cell::RefCell;
use std::rc::Rc;
@ -134,3 +135,25 @@ pub fn find_expressible_symbol(scope: &Scope, name: &str) -> Option<ExpressibleS
Scope::Block(block_scope) => find_expressible_in_block_by_name(block_scope, name),
}
}
fn find_type_symbol_in_module(module_scope: &ModuleScope, name: &str) -> Option<TypeSymbol> {
module_scope
.class_symbols()
.get(name)
.map(|symbol| TypeSymbol::Class(symbol.clone()))
}
fn find_type_symbol_in_class(class_scope: &ClassScope, name: &str) -> Option<TypeSymbol> {
class_scope
.class_symbols()
.get(name)
.map(|symbol| TypeSymbol::Class(symbol.clone()))
}
pub fn find_type_symbol(scope: &Scope, name: &str) -> Option<TypeSymbol> {
match scope {
Scope::Module(module_scope) => find_type_symbol_in_module(module_scope, name),
Scope::Class(class_scope) => find_type_symbol_in_class(class_scope, name),
_ => None,
}
}

View File

@ -6,13 +6,16 @@ use crate::scope::class_scope::ClassScope;
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::expressible_symbol::ExpressibleSymbol;
use crate::symbol::field_symbol::FieldSymbol;
use crate::symbol::function_symbol::FunctionSymbol;
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_in_function_by_name, find_in_module_by_name, find_type_symbol,
};
use std::cell::RefCell;
use std::rc::Rc;
@ -84,6 +87,72 @@ impl SymbolTable {
&mut self.scopes[self.current_scope_id.unwrap()]
}
pub fn insert_class_symbol(
&mut self,
class_symbol: ClassSymbol,
) -> Result<Rc<RefCell<ClassSymbol>>, SymbolInsertError> {
let maybe_already_inserted = match self.current_scope() {
Scope::Module(module_scope) => {
find_in_module_by_name(module_scope, class_symbol.declared_name())
}
Scope::Class(class_scope) => {
find_in_class_by_name(class_scope, class_symbol.declared_name())
}
_ => panic!("Attempt to insert ClassSymbol in incompatible scope"),
};
if let Some(already_inserted) = maybe_already_inserted {
return Err(SymbolInsertError::AlreadyDeclared(AlreadyDeclared::new(
already_inserted,
)));
}
let name = class_symbol.declared_name_owned();
let as_rc = Rc::new(RefCell::new(class_symbol));
let to_return = as_rc.clone();
match self.current_scope_mut() {
Scope::Module(module_scope) => {
module_scope.class_symbols_mut().insert(name, as_rc);
}
Scope::Class(class_scope) => {
class_scope.class_symbols_mut().insert(name, as_rc);
}
_ => unreachable!(),
}
Ok(to_return)
}
pub fn insert_field_symbol(
&mut self,
field_symbol: FieldSymbol,
) -> Result<Rc<RefCell<FieldSymbol>>, SymbolInsertError> {
let maybe_already_inserted = match self.current_scope() {
Scope::Class(class_scope) => {
find_in_class_by_name(class_scope, field_symbol.declared_name())
}
_ => panic!("Attempt to insert FieldSymbol in incompatible scope"),
};
if let Some(already_inserted) = maybe_already_inserted {
return Err(SymbolInsertError::AlreadyDeclared(AlreadyDeclared::new(
already_inserted,
)));
}
let name = field_symbol.declared_name_owned();
let as_rc = Rc::new(RefCell::new(field_symbol));
let to_return = as_rc.clone();
match self.current_scope_mut() {
Scope::Class(class_scope) => {
class_scope.field_symbols_mut().insert(name, as_rc);
}
_ => unreachable!(),
}
Ok(to_return)
}
pub fn insert_function_symbol(
&mut self,
function_symbol: FunctionSymbol,
@ -198,6 +267,22 @@ impl SymbolTable {
None
}
pub fn find_type_symbol(&self, scope_id: usize, name: &str) -> Option<TypeSymbol> {
let mut maybe_scope = self.scopes.get(scope_id);
if maybe_scope.is_none() {
panic!("Invalid scope_id: {}", scope_id);
}
while let Some(scope) = maybe_scope {
let maybe_type_symbol = find_type_symbol(scope, name);
if maybe_type_symbol.is_some() {
return maybe_type_symbol;
} else {
maybe_scope = scope.parent_id().map(|id| &self.scopes[id]);
}
}
None
}
pub fn get_variable_symbol(&self, scope_id: usize, name: &str) -> Rc<RefCell<VariableSymbol>> {
match &self.scopes[scope_id] {
Scope::Block(block_scope) => block_scope.variable_symbols().get(name).cloned().unwrap(),

View File

@ -1,3 +1,5 @@
use crate::symbol::Symbol;
use crate::symbol::class_symbol::ClassSymbol;
use crate::symbol::function_symbol::FunctionSymbol;
use std::cell::RefCell;
use std::fmt::{Display, Formatter};
@ -10,6 +12,7 @@ pub enum TypeInfo {
Double,
String,
Function(Rc<RefCell<FunctionSymbol>>),
Class(Rc<RefCell<ClassSymbol>>),
Void,
}
@ -27,6 +30,9 @@ impl Display for TypeInfo {
}
write!(f, ")")
}
TypeInfo::Class(class_symbol) => {
write!(f, "{}", class_symbol.borrow().declared_name())
}
TypeInfo::Void => write!(f, "Void"),
}
}
@ -34,6 +40,7 @@ impl Display for TypeInfo {
impl TypeInfo {
// This is very naive but works for now
#[deprecated]
pub fn from_declared_name(declared_name: &str) -> Self {
match declared_name {
"Any" => TypeInfo::Any,
@ -60,6 +67,15 @@ impl TypeInfo {
TypeInfo::Function(_) => {
unimplemented!("Type matching on Functions not yet supported.")
}
TypeInfo::Class(class_symbol) => {
match other {
TypeInfo::Class(other_class_symbol) => {
class_symbol.borrow().declared_name()
== other_class_symbol.borrow().declared_name() // good enough for now
}
_ => false,
}
}
TypeInfo::Void => {
matches!(other, TypeInfo::Void)
}