Use ast walking to verify that identifiers have saved symbols and linking symbols are resolved.

This commit is contained in:
Jesse Brault 2025-05-26 16:24:40 -05:00
parent d38b30b755
commit 234f40ec58
4 changed files with 132 additions and 34 deletions

View File

@ -3,4 +3,4 @@ pub mod children;
pub mod node; pub mod node;
pub mod pretty_print; pub mod pretty_print;
pub mod unparse; pub mod unparse;
mod walk; pub mod walk;

View File

@ -250,9 +250,9 @@ fn gather_parameter(
match insert_result { match insert_result {
Ok(parameter_symbol) => { Ok(parameter_symbol) => {
parameter let mut identifier = parameter.identifier_mut();
.identifier_mut() identifier.set_scope_id(symbol_table.current_scope_id());
.set_scope_id(symbol_table.current_scope_id()); identifier.set_saved_symbol(Symbol::Parameter(parameter_symbol.clone()));
gather_type_use( gather_type_use(
parameter.type_use_mut(), parameter.type_use_mut(),
@ -694,9 +694,9 @@ fn gather_function_definition(
match insert_result { match insert_result {
Ok(function_symbol) => { Ok(function_symbol) => {
function let mut identifier = function.identifier_mut();
.identifier_mut() identifier.set_saved_symbol(Symbol::Function(function_symbol.clone()));
.set_scope_id(symbol_table.current_scope_id()); identifier.set_scope_id(symbol_table.current_scope_id());
symbol_table.push_scope(&format!("FunctionParameterScope({})", resolved_name)); symbol_table.push_scope(&format!("FunctionParameterScope({})", resolved_name));
@ -1043,21 +1043,22 @@ fn gather_variable_declaration(
Some(identifier), Some(identifier),
)); ));
if let Err(err) = insert_result { match insert_result {
handle_insert_error( Ok(variable_symbol) => {
let mut identifier = variable_declaration.identifier_mut();
identifier.set_saved_symbol(Symbol::Variable(variable_symbol));
identifier.set_scope_id(symbol_table.current_scope_id());
}
Err(err) => handle_insert_error(
err, err,
&variable_name, &variable_name,
identifier.file_id(), identifier.file_id(),
identifier.range(), identifier.range(),
"function/variable", "function/variable",
diagnostics, diagnostics,
) ),
} }
variable_declaration
.identifier_mut()
.set_scope_id(symbol_table.current_scope_id());
if let Some(initializer) = variable_declaration.initializer_mut() { if let Some(initializer) = variable_declaration.initializer_mut() {
gather_expression(initializer, symbol_table, diagnostics); gather_expression(initializer, symbol_table, diagnostics);
} }

View File

@ -55,6 +55,9 @@ pub fn analyze_names(
mod tests { mod tests {
use super::*; use super::*;
use crate::ast::build::build_ast; use crate::ast::build::build_ast;
use crate::ast::children::NodeRef;
use crate::ast::node::use_statement::UseStatementLast;
use crate::ast::walk::walk_depth_first;
use crate::parser::{DeimosParser, Rule}; use crate::parser::{DeimosParser, Rule};
use crate::std_core::add_std_core_symbols; use crate::std_core::add_std_core_symbols;
use codespan_reporting::files::SimpleFiles; use codespan_reporting::files::SimpleFiles;
@ -68,7 +71,7 @@ mod tests {
sources: HashMap<&str, &str>, sources: HashMap<&str, &str>,
symbol_table: &mut SymbolTable, symbol_table: &mut SymbolTable,
n_diagnostics: usize, n_diagnostics: usize,
) { ) -> Vec<CompilationUnit> {
let mut files = SimpleFiles::new(); let mut files = SimpleFiles::new();
let mut compilation_units = vec![]; let mut compilation_units = vec![];
@ -83,12 +86,12 @@ mod tests {
panic!("Parsing did not consume entire input."); panic!("Parsing did not consume entire input.");
} }
compilation_units.push(build_ast(file_name, file_id, pairs.next().unwrap())) compilation_units.push(build_ast(file_name, file_id, pairs.next().unwrap()));
} }
let diagnostics = analyze_names(&mut compilation_units, symbol_table); let diagnostics = analyze_names(&mut compilation_units, symbol_table);
if !diagnostics.is_empty() { if diagnostics.len() != n_diagnostics {
let writer = StandardStream::stderr(ColorChoice::Always); let writer = StandardStream::stderr(ColorChoice::Always);
let config = term::Config::default(); let config = term::Config::default();
@ -101,13 +104,88 @@ mod tests {
assert_eq!(n_diagnostics, diagnostics.len()); assert_eq!(n_diagnostics, diagnostics.len());
for compilation_unit in &compilation_units { compilation_units
dbg!(compilation_unit);
}
} }
fn assert_no_diagnostics(sources: HashMap<&str, &str>, symbol_table: &mut SymbolTable) { fn assert_no_diagnostics(
assert_number_of_diagnostics(sources, symbol_table, 0); sources: HashMap<&str, &str>,
symbol_table: &mut SymbolTable,
) -> Vec<CompilationUnit> {
assert_number_of_diagnostics(sources, symbol_table, 0)
}
fn assert_saved_symbols(compilation_unit: &CompilationUnit) {
walk_depth_first(compilation_unit, &mut |node_ref| match node_ref {
NodeRef::Identifier(identifier) => {
if identifier.saved_symbol().is_none() {
panic!("{:?} does not have a saved symbol.", identifier)
}
}
NodeRef::FullyQualifiedName(fqn) => {
if fqn.saved_symbol().is_none() {
panic!("{:?} does not have a saved symbol.", fqn)
}
}
NodeRef::UseStatement(use_statement) => match use_statement.last() {
UseStatementLast::Identifier(identifier) => {
if identifier.saved_symbol().is_none() {
panic!(
"UseStatement {:?} does not have a saved symbol.",
identifier
)
}
}
UseStatementLast::Identifiers(identifiers) => {
for identifier in identifiers {
if identifier.saved_symbol().is_none() {
panic!(
"UseStatement {:?} does not have a saved symbol.",
identifier
)
}
}
}
_ => todo!(),
},
_ => {}
})
}
fn assert_resolved_symbols(compilation_unit: &CompilationUnit) {
walk_depth_first(compilation_unit, &mut |node_ref| match node_ref {
NodeRef::UseStatement(use_statement) => match use_statement.last() {
UseStatementLast::Identifier(identifier) => {
let use_statement_symbol = identifier
.saved_symbol()
.unwrap()
.unwrap_use_statement_symbol();
let borrowed = use_statement_symbol.borrow();
if borrowed.referenced_symbol().is_none() {
panic!(
"{:?} does not have a referenced symbol.",
use_statement_symbol
)
}
}
UseStatementLast::Identifiers(identifiers) => {
for identifier in identifiers {
let use_statement_symbol = identifier
.saved_symbol()
.unwrap()
.unwrap_use_statement_symbol();
let borrowed = use_statement_symbol.borrow();
if borrowed.referenced_symbol().is_none() {
panic!(
"{:?} does not have a referenced symbol.",
use_statement_symbol
)
}
}
}
_ => todo!(),
},
_ => {}
})
} }
#[test] #[test]
@ -120,7 +198,10 @@ mod tests {
}"}, }"},
)]); )]);
assert_no_diagnostics(sources, &mut SymbolTable::new()); let cus = assert_no_diagnostics(sources, &mut SymbolTable::new());
for ref cu in cus {
assert_saved_symbols(cu);
}
} }
#[test] #[test]
@ -142,7 +223,11 @@ mod tests {
), ),
]); ]);
assert_no_diagnostics(sources, &mut SymbolTable::new()); let cus = assert_no_diagnostics(sources, &mut SymbolTable::new());
for ref cu in cus {
assert_saved_symbols(cu);
assert_resolved_symbols(cu);
}
} }
#[test] #[test]
@ -158,7 +243,11 @@ mod tests {
let mut symbol_table = SymbolTable::new(); let mut symbol_table = SymbolTable::new();
add_std_core_symbols(&mut symbol_table).expect("Failed to add std::core symbols."); add_std_core_symbols(&mut symbol_table).expect("Failed to add std::core symbols.");
assert_no_diagnostics(sources, &mut symbol_table); let cus = assert_no_diagnostics(sources, &mut symbol_table);
for ref cu in cus {
assert_saved_symbols(cu);
assert_resolved_symbols(cu);
}
} }
#[test] #[test]
@ -194,7 +283,11 @@ mod tests {
), ),
]); ]);
let mut symbol_table = SymbolTable::new(); let mut symbol_table = SymbolTable::new();
assert_no_diagnostics(sources, &mut symbol_table); let cus = assert_no_diagnostics(sources, &mut symbol_table);
for ref cu in cus {
assert_saved_symbols(cu);
assert_resolved_symbols(cu);
}
} }
#[test] #[test]

View File

@ -311,27 +311,31 @@ impl SymbolTable {
pub fn insert_variable_symbol( pub fn insert_variable_symbol(
&mut self, &mut self,
variable_symbol: VariableSymbol, variable_symbol: VariableSymbol,
) -> Result<(), SymbolInsertError> { ) -> Result<Rc<VariableSymbol>, SymbolInsertError> {
let current_scope = self.scopes.get_mut(self.current_scope_id).unwrap(); let current_scope = self.scopes.get_mut(self.current_scope_id).unwrap();
if let Some(defined_symbol) = if let Some(defined_symbol) =
current_scope.get_value_symbol_by_declared_name(variable_symbol.declared_name()) current_scope.get_value_symbol_by_declared_name(variable_symbol.declared_name())
{ {
Err(SymbolAlreadyDefined(defined_symbol)) Err(SymbolAlreadyDefined(defined_symbol))
} else { } else {
current_scope.variable_symbols.insert( let declared_name = variable_symbol.declared_name().to_string();
variable_symbol.declared_name().to_string(), let to_insert = Rc::new(variable_symbol);
Rc::new(variable_symbol), let to_return = to_insert.clone();
); current_scope
Ok(()) .variable_symbols
.insert(declared_name, to_insert);
Ok(to_return)
} }
} }
pub fn insert_class_member_symbol( pub fn insert_class_member_symbol(
&mut self, &mut self,
class_member_symbol: ClassMemberSymbol, class_member_symbol: ClassMemberSymbol,
) -> Result<(), SymbolInsertError> { ) -> Result<(), SymbolInsertError> {
let current_scope = self.scopes.get_mut(self.current_scope_id).unwrap(); let current_scope = self.scopes.get_mut(self.current_scope_id).unwrap();
if let Some(defined_symbol) = current_scope.get_expressible_by_declared_name(class_member_symbol.declared_name()) { if let Some(defined_symbol) =
current_scope.get_expressible_by_declared_name(class_member_symbol.declared_name())
{
Err(SymbolAlreadyDefined(defined_symbol)) Err(SymbolAlreadyDefined(defined_symbol))
} else { } else {
current_scope.class_member_symbols.insert( current_scope.class_member_symbols.insert(