use std::rc::Rc;
use semver::Version;
use crate::backend::binder::{Binder, Definition, FileScope, ParametersScope, Scope, ScopeId};
use crate::backend::ir::ir2_flat_contracts::visitor::Visitor;
use crate::backend::ir::ir2_flat_contracts::{self as input_ir};
use crate::backend::semantic::SemanticAnalysis;
use crate::compilation::File;
use crate::cst::{NodeId, TerminalNode};
use crate::utils::versions::VERSION_0_5_0;
pub fn run(semantic_analysis: &mut SemanticAnalysis) {
let mut pass = Pass::new(
semantic_analysis.language_version().clone(),
&mut semantic_analysis.binder,
);
for semantic_file in semantic_analysis.files.values() {
pass.visit_file(semantic_file.file(), semantic_file.ir_root());
}
}
struct ScopeFrame {
structural_scope_id: ScopeId,
lexical_scope_id: ScopeId,
}
struct Pass<'a> {
language_version: Version,
current_file: Option<Rc<File>>, scope_stack: Vec<ScopeFrame>,
binder: &'a mut Binder,
}
impl<'a> Pass<'a> {
fn new(language_version: Version, binder: &'a mut Binder) -> Self {
Self {
language_version,
current_file: None,
scope_stack: Vec::new(),
binder,
}
}
fn visit_file(&mut self, file: &Rc<File>, source_unit: &input_ir::SourceUnit) {
assert!(self.current_file.is_none());
self.current_file = Some(Rc::clone(file));
input_ir::visitor::accept_source_unit(source_unit, self);
self.current_file = None;
assert!(self.scope_stack.is_empty());
}
fn enter_scope(&mut self, scope: Scope) -> ScopeId {
let scope_id = self.binder.insert_scope(scope);
self.scope_stack.push(ScopeFrame {
structural_scope_id: scope_id,
lexical_scope_id: scope_id,
});
scope_id
}
fn replace_scope(&mut self, scope: Scope) -> ScopeId {
let Some(ScopeFrame {
structural_scope_id,
..
}) = self.scope_stack.pop()
else {
unreachable!("scope stack cannot be empty");
};
let scope_id = self.binder.insert_scope(scope);
self.scope_stack.push(ScopeFrame {
structural_scope_id,
lexical_scope_id: scope_id,
});
scope_id
}
fn enter_scope_for_node_id(&mut self, node_id: NodeId) {
let scope_id = self.binder.scope_id_for_node_id(node_id).unwrap();
self.scope_stack.push(ScopeFrame {
structural_scope_id: scope_id,
lexical_scope_id: scope_id,
});
}
fn leave_scope_for_node_id(&mut self, node_id: NodeId) {
let Some(ScopeFrame {
structural_scope_id,
..
}) = self.scope_stack.pop()
else {
unreachable!("attempt to pop an empty scope stack");
};
assert_eq!(
structural_scope_id,
self.binder.scope_id_for_node_id(node_id).unwrap()
);
}
fn current_scope_id(&self) -> ScopeId {
let Some(ScopeFrame {
lexical_scope_id, ..
}) = self.scope_stack.last()
else {
unreachable!("empty scope stack");
};
*lexical_scope_id
}
fn current_scope(&mut self) -> &mut Scope {
let scope_id = self.current_scope_id();
self.binder.get_scope_mut(scope_id)
}
fn current_file_scope(&mut self) -> &mut FileScope {
let Scope::File(file_scope) = self.current_scope() else {
unreachable!("current scope is not a file scope");
};
file_scope
}
fn insert_definition_in_current_scope(&mut self, definition: Definition) {
self.binder
.insert_definition_in_scope(definition, self.current_scope_id());
}
fn resolve_import_path(&self, import_path: &Rc<TerminalNode>) -> Option<String> {
let import_path_node_id = import_path.id();
let current_file = self
.current_file
.as_ref()
.expect("import directive must be visited in the context of a current file");
current_file
.resolved_import_by_node_id(import_path_node_id)
.map(|file_id| file_id.to_string())
}
fn collect_parameters(&mut self, parameters: &input_ir::Parameters) -> ScopeId {
let mut scope = ParametersScope::new();
for parameter in parameters {
scope.add_parameter(parameter.name.as_ref(), parameter.node_id);
if parameter.name.is_some() {
let definition = Definition::new_parameter(parameter);
self.binder.insert_definition_no_scope(definition);
}
}
self.binder.insert_scope(Scope::Parameters(scope))
}
fn collect_named_parameters_into_scope(
&mut self,
parameters: &input_ir::Parameters,
scope_id: ScopeId,
) {
for parameter in parameters {
if parameter.name.is_some() {
let definition = Definition::new_parameter(parameter);
self.binder.insert_definition_in_scope(definition, scope_id);
}
}
}
fn register_constructor_parameters(&mut self, constructor_parameters_scope_id: ScopeId) {
let current_scope_node_id = self.current_scope().node_id();
let Definition::Contract(contract_definition) =
self.binder.get_definition_mut(current_scope_node_id)
else {
unreachable!("the current scope is not a contract");
};
contract_definition.constructor_parameters_scope_id = Some(constructor_parameters_scope_id);
}
}
impl Visitor for Pass<'_> {
fn enter_source_unit(&mut self, node: &input_ir::SourceUnit) -> bool {
let Some(current_file) = &self.current_file else {
unreachable!("visiting SourceUnit without a current file being set");
};
let scope = Scope::new_file(node.node_id, current_file.id());
self.enter_scope(scope);
true
}
fn leave_source_unit(&mut self, node: &input_ir::SourceUnit) {
self.leave_scope_for_node_id(node.node_id);
}
fn enter_contract_definition(&mut self, node: &input_ir::ContractDefinition) -> bool {
let definition = Definition::new_contract(node);
self.insert_definition_in_current_scope(definition);
let scope = Scope::new_contract(node.node_id, self.current_scope_id());
self.enter_scope(scope);
true
}
fn leave_contract_definition(&mut self, node: &input_ir::ContractDefinition) {
self.leave_scope_for_node_id(node.node_id);
}
fn enter_library_definition(&mut self, node: &input_ir::LibraryDefinition) -> bool {
let definition = Definition::new_library(node);
self.insert_definition_in_current_scope(definition);
let scope = Scope::new_contract(node.node_id, self.current_scope_id());
self.enter_scope(scope);
true
}
fn leave_library_definition(&mut self, node: &input_ir::LibraryDefinition) {
self.leave_scope_for_node_id(node.node_id);
}
fn enter_interface_definition(&mut self, node: &input_ir::InterfaceDefinition) -> bool {
let definition = Definition::new_interface(node);
self.insert_definition_in_current_scope(definition);
let scope = Scope::new_contract(node.node_id, self.current_scope_id());
self.enter_scope(scope);
true
}
fn leave_interface_definition(&mut self, node: &input_ir::InterfaceDefinition) {
self.leave_scope_for_node_id(node.node_id);
}
fn enter_path_import(&mut self, node: &input_ir::PathImport) -> bool {
let imported_file_id = self.resolve_import_path(&node.path);
if node.alias.is_some() {
let definition = Definition::new_import(node, imported_file_id);
self.insert_definition_in_current_scope(definition);
} else if let Some(imported_file_id) = imported_file_id {
self.current_file_scope()
.add_imported_file(imported_file_id);
}
false
}
fn enter_import_deconstruction(&mut self, node: &input_ir::ImportDeconstruction) -> bool {
let imported_file_id = self.resolve_import_path(&node.path);
for symbol in &node.symbols {
let definition = Definition::new_imported_symbol(
symbol,
symbol.name.unparse(),
imported_file_id.clone(),
);
self.insert_definition_in_current_scope(definition);
}
false
}
fn enter_function_definition(&mut self, node: &input_ir::FunctionDefinition) -> bool {
match node.kind {
input_ir::FunctionKind::Regular
| input_ir::FunctionKind::Constructor
| input_ir::FunctionKind::Fallback
| input_ir::FunctionKind::Receive
| input_ir::FunctionKind::Unnamed => {
let parameters_scope_id = self.collect_parameters(&node.parameters);
if let Some(name) = &node.name {
let visibility = (&node.visibility).into();
let definition =
Definition::new_function(node, parameters_scope_id, visibility);
let current_scope_node_id = self.current_scope().node_id();
let enclosing_definition =
self.binder.find_definition_by_id(current_scope_node_id);
let enclosing_contract_name =
if let Some(enclosing_definition) = enclosing_definition {
if matches!(enclosing_definition, Definition::Contract(_)) {
Some(enclosing_definition.identifier().unparse())
} else {
None
}
} else {
None
};
if enclosing_contract_name
.is_some_and(|contract_name| contract_name == name.unparse())
{
self.binder.insert_definition_no_scope(definition);
if self.language_version < VERSION_0_5_0 {
self.register_constructor_parameters(parameters_scope_id);
}
} else {
self.insert_definition_in_current_scope(definition);
}
} else if matches!(node.kind, input_ir::FunctionKind::Constructor) {
self.register_constructor_parameters(parameters_scope_id);
}
let function_scope =
Scope::new_function(node.node_id, self.current_scope_id(), parameters_scope_id);
let function_scope_id = self.enter_scope(function_scope);
if let Some(returns) = &node.returns {
self.collect_named_parameters_into_scope(returns, function_scope_id);
}
}
input_ir::FunctionKind::Modifier => {
let definition = Definition::new_modifier(node);
self.insert_definition_in_current_scope(definition);
let modifier_scope = Scope::new_modifier(node.node_id, self.current_scope_id());
let modifier_scope_id = self.enter_scope(modifier_scope);
self.collect_named_parameters_into_scope(&node.parameters, modifier_scope_id);
}
}
true
}
fn leave_function_definition(&mut self, node: &input_ir::FunctionDefinition) {
self.leave_scope_for_node_id(node.node_id);
}
fn enter_enum_definition(&mut self, node: &input_ir::EnumDefinition) -> bool {
let definition = Definition::new_enum(node);
self.insert_definition_in_current_scope(definition);
let enum_scope = Scope::new_enum(node.node_id);
let enum_scope_id = self.binder.insert_scope(enum_scope);
for member in &node.members {
let definition = Definition::new_enum_member(member);
self.binder
.insert_definition_in_scope(definition, enum_scope_id);
}
false
}
fn enter_struct_definition(&mut self, node: &input_ir::StructDefinition) -> bool {
let definition = Definition::new_struct(node);
self.insert_definition_in_current_scope(definition);
let struct_scope = Scope::new_struct(node.node_id);
let struct_scope_id = self.binder.insert_scope(struct_scope);
for member in &node.members {
let definition = Definition::new_struct_member(member);
self.binder
.insert_definition_in_scope(definition, struct_scope_id);
}
true
}
fn enter_error_definition(&mut self, node: &input_ir::ErrorDefinition) -> bool {
let parameters_scope_id = self.collect_parameters(&node.parameters);
let definition = Definition::new_error(node, parameters_scope_id);
self.insert_definition_in_current_scope(definition);
false
}
fn enter_event_definition(&mut self, node: &input_ir::EventDefinition) -> bool {
let parameters_scope_id = self.collect_parameters(&node.parameters);
let definition = Definition::new_event(node, parameters_scope_id);
self.insert_definition_in_current_scope(definition);
false
}
fn enter_state_variable_definition(
&mut self,
node: &input_ir::StateVariableDefinition,
) -> bool {
let visibility = (&node.visibility).into();
let definition = Definition::new_state_variable(node, visibility);
self.insert_definition_in_current_scope(definition);
true
}
fn enter_constant_definition(&mut self, node: &input_ir::ConstantDefinition) -> bool {
let definition = Definition::new_constant(node);
self.insert_definition_in_current_scope(definition);
false
}
fn enter_user_defined_value_type_definition(
&mut self,
node: &input_ir::UserDefinedValueTypeDefinition,
) -> bool {
let definition = Definition::new_user_defined_value_type(node);
self.insert_definition_in_current_scope(definition);
false
}
fn leave_variable_declaration_statement(
&mut self,
node: &input_ir::VariableDeclarationStatement,
) {
if self.language_version >= VERSION_0_5_0 {
let scope = Scope::new_block(node.node_id, self.current_scope_id());
self.replace_scope(scope);
}
let definition = Definition::new_variable(node);
self.insert_definition_in_current_scope(definition);
}
fn enter_block(&mut self, node: &input_ir::Block) -> bool {
if self.language_version >= VERSION_0_5_0 {
let scope = Scope::new_block(node.node_id, self.current_scope_id());
self.enter_scope(scope);
}
true
}
fn leave_block(&mut self, node: &input_ir::Block) {
if self.language_version >= VERSION_0_5_0 {
self.leave_scope_for_node_id(node.node_id);
}
}
fn enter_for_statement(&mut self, node: &input_ir::ForStatement) -> bool {
if self.language_version >= VERSION_0_5_0 {
let scope = Scope::new_block(node.node_id, self.current_scope_id());
self.enter_scope(scope);
}
true
}
fn leave_for_statement(&mut self, node: &input_ir::ForStatement) {
if self.language_version >= VERSION_0_5_0 {
self.leave_scope_for_node_id(node.node_id);
}
}
fn leave_try_statement(&mut self, node: &input_ir::TryStatement) {
if self.language_version < VERSION_0_5_0 {
return;
}
if let Some(returns) = &node.returns {
let body_scope_id = self.binder.scope_id_for_node_id(node.body.node_id).unwrap();
self.collect_named_parameters_into_scope(returns, body_scope_id);
}
}
fn leave_catch_clause(&mut self, node: &input_ir::CatchClause) {
if let Some(error) = &node.error {
let body_scope_id = self.binder.scope_id_for_node_id(node.body.node_id).unwrap();
self.collect_named_parameters_into_scope(&error.parameters, body_scope_id);
}
}
fn enter_mapping_type(&mut self, node: &input_ir::MappingType) -> bool {
if node.key_type.name.is_some() {
let definition = Definition::new_type_parameter(&node.key_type);
self.binder.insert_definition_no_scope(definition);
}
if node.value_type.name.is_some() {
let definition = Definition::new_type_parameter(&node.value_type);
self.binder.insert_definition_no_scope(definition);
}
true
}
fn enter_function_type(&mut self, node: &input_ir::FunctionType) -> bool {
for parameter in &node.parameters {
if parameter.name.is_some() {
let definition = Definition::new_type_parameter(parameter);
self.binder.insert_definition_no_scope(definition);
}
}
if let Some(returns) = &node.returns {
for parameter in returns {
if parameter.name.is_some() {
let definition = Definition::new_type_parameter(parameter);
self.binder.insert_definition_no_scope(definition);
}
}
}
false
}
fn enter_yul_block(&mut self, node: &input_ir::YulBlock) -> bool {
let scope = Scope::new_yul_block(node.node_id, self.current_scope_id());
self.enter_scope(scope);
true
}
fn leave_yul_block(&mut self, node: &input_ir::YulBlock) {
self.leave_scope_for_node_id(node.node_id);
}
fn enter_yul_function_definition(&mut self, node: &input_ir::YulFunctionDefinition) -> bool {
let definition = Definition::new_yul_function(node);
self.insert_definition_in_current_scope(definition);
let scope = Scope::new_yul_function(node.node_id, self.current_scope_id());
let scope_id = self.enter_scope(scope);
for parameter in &node.parameters {
let definition = Definition::new_yul_parameter(parameter);
self.binder.insert_definition_in_scope(definition, scope_id);
}
if let Some(returns) = &node.returns {
for parameter in returns {
let definition = Definition::new_yul_variable(parameter);
self.binder.insert_definition_in_scope(definition, scope_id);
}
}
true
}
fn leave_yul_function_definition(&mut self, node: &input_ir::YulFunctionDefinition) {
self.leave_scope_for_node_id(node.node_id);
}
fn enter_yul_label(&mut self, node: &input_ir::YulLabel) -> bool {
let definition = Definition::new_yul_label(node);
self.insert_definition_in_current_scope(definition);
false
}
fn enter_yul_variable_declaration_statement(
&mut self,
node: &input_ir::YulVariableDeclarationStatement,
) -> bool {
for variable in &node.variables {
let definition = Definition::new_yul_variable(variable);
self.insert_definition_in_current_scope(definition);
}
false
}
fn enter_yul_for_statement(&mut self, node: &input_ir::YulForStatement) -> bool {
input_ir::visitor::accept_yul_block(&node.initialization, self);
self.enter_scope_for_node_id(node.initialization.node_id);
input_ir::visitor::accept_yul_expression(&node.condition, self);
input_ir::visitor::accept_yul_block(&node.iterator, self);
input_ir::visitor::accept_yul_block(&node.body, self);
self.leave_scope_for_node_id(node.initialization.node_id);
false
}
}