diff --git a/crates/solidity-v2/outputs/cargo/ast/generated/public_api.txt b/crates/solidity-v2/outputs/cargo/ast/generated/public_api.txt index 536ac6ae5f..947d4b208e 100644 --- a/crates/solidity-v2/outputs/cargo/ast/generated/public_api.txt +++ b/crates/solidity-v2/outputs/cargo/ast/generated/public_api.txt @@ -1427,16 +1427,16 @@ pub fn slang_solidity_v2_ast::ast::ContractDefinitionStruct::references(&self) - impl slang_solidity_v2_ast::ast::ContractDefinitionStruct pub fn slang_solidity_v2_ast::ast::ContractDefinitionStruct::compute_abi(&self) -> core::option::Option impl slang_solidity_v2_ast::ast::ContractDefinitionStruct -pub fn slang_solidity_v2_ast::ast::ContractDefinitionStruct::compute_linearised_bases(&self) -> alloc::vec::Vec -pub fn slang_solidity_v2_ast::ast::ContractDefinitionStruct::compute_linearised_errors(&self) -> alloc::vec::Vec -pub fn slang_solidity_v2_ast::ast::ContractDefinitionStruct::compute_linearised_events(&self) -> alloc::vec::Vec -pub fn slang_solidity_v2_ast::ast::ContractDefinitionStruct::compute_linearised_functions(&self) -> alloc::vec::Vec -pub fn slang_solidity_v2_ast::ast::ContractDefinitionStruct::compute_linearised_state_variables(&self) -> alloc::vec::Vec pub fn slang_solidity_v2_ast::ast::ContractDefinitionStruct::constructor(&self) -> core::option::Option pub fn slang_solidity_v2_ast::ast::ContractDefinitionStruct::direct_bases(&self) -> alloc::vec::Vec pub fn slang_solidity_v2_ast::ast::ContractDefinitionStruct::errors(&self) -> alloc::vec::Vec pub fn slang_solidity_v2_ast::ast::ContractDefinitionStruct::events(&self) -> alloc::vec::Vec pub fn slang_solidity_v2_ast::ast::ContractDefinitionStruct::functions(&self) -> alloc::vec::Vec +pub fn slang_solidity_v2_ast::ast::ContractDefinitionStruct::linearised_bases(&self) -> alloc::vec::Vec +pub fn slang_solidity_v2_ast::ast::ContractDefinitionStruct::linearised_errors(&self) -> alloc::vec::Vec +pub fn slang_solidity_v2_ast::ast::ContractDefinitionStruct::linearised_events(&self) -> alloc::vec::Vec +pub fn slang_solidity_v2_ast::ast::ContractDefinitionStruct::linearised_functions(&self) -> alloc::vec::Vec +pub fn slang_solidity_v2_ast::ast::ContractDefinitionStruct::linearised_state_variables(&self) -> alloc::vec::Vec pub fn slang_solidity_v2_ast::ast::ContractDefinitionStruct::modifiers(&self) -> alloc::vec::Vec pub fn slang_solidity_v2_ast::ast::ContractDefinitionStruct::state_variables(&self) -> alloc::vec::Vec impl serde_core::ser::Serialize for slang_solidity_v2_ast::ast::ContractDefinitionStruct diff --git a/crates/solidity-v2/outputs/cargo/ast/src/abi/mod.rs b/crates/solidity-v2/outputs/cargo/ast/src/abi/mod.rs index 2d60faeb3f..e89d1680cc 100644 --- a/crates/solidity-v2/outputs/cargo/ast/src/abi/mod.rs +++ b/crates/solidity-v2/outputs/cargo/ast/src/abi/mod.rs @@ -428,6 +428,9 @@ fn type_as_abi_parameter_impl( visited_structs.remove(definition_id); Some(("tuple".to_string(), components)) } - _ => Some((semantic.type_canonical_name(type_id)?, Vec::new())), + _ => Some(( + semantic.type_canonical_name(type_id)?.to_owned(), + Vec::new(), + )), } } diff --git a/crates/solidity-v2/outputs/cargo/ast/src/abi/node_extensions/contract_definition.rs b/crates/solidity-v2/outputs/cargo/ast/src/abi/node_extensions/contract_definition.rs index d5a1eb2f6d..8266f90151 100644 --- a/crates/solidity-v2/outputs/cargo/ast/src/abi/node_extensions/contract_definition.rs +++ b/crates/solidity-v2/outputs/cargo/ast/src/abi/node_extensions/contract_definition.rs @@ -1,6 +1,6 @@ use ruint::aliases::U256; use slang_solidity_v2_semantic::binder; -use slang_solidity_v2_semantic::context::SemanticContext; +use slang_solidity_v2_semantic::context::SLOT_SIZE; use crate::abi::{AbiEntry, ContractAbi, StorageItem}; use crate::ast::{ContractDefinitionStruct, StateVariableDefinition, StateVariableMutability}; @@ -26,20 +26,20 @@ impl ContractDefinitionStruct { if let Some(constructor) = self.constructor() { entries.push(constructor.compute_abi_entry()?); } - for function in &self.compute_linearised_functions() { + for function in &self.linearised_functions() { if function.is_externally_visible() { entries.push(function.compute_abi_entry()?); } } - for state_variable in &self.compute_linearised_state_variables() { + for state_variable in &self.linearised_state_variables() { if state_variable.is_externally_visible() { entries.push(state_variable.compute_abi_entry()?); } } - for error in &self.compute_linearised_errors() { + for error in &self.linearised_errors() { entries.push(error.compute_abi_entry()?); } - for event in &self.compute_linearised_events() { + for event in &self.linearised_events() { entries.push(event.compute_abi_entry()?); } @@ -63,7 +63,7 @@ impl ContractDefinitionStruct { /// Computes the layouts of both permanent and transient state variables fn compute_storage_layout(&self) -> Option<(Vec, Vec)> { - let all_state_variables = self.compute_linearised_state_variables(); + let all_state_variables = self.linearised_state_variables(); // TODO(validation) SDR[2]: it is an error if any contract in the hierarchy // other than the leaf has a custom offset layout @@ -103,14 +103,17 @@ impl ContractDefinitionStruct { // Check if we can pack the variable in the current slot, otherwise // we start at the beginning of the next slot. - let remaining_bytes = SemanticContext::SLOT_SIZE - byte_offset_in_slot; + let remaining_bytes = SLOT_SIZE - byte_offset_in_slot; if byte_offset_in_slot > 0 && variable_size > remaining_bytes { current_slot += U256::from(1u64); byte_offset_in_slot = 0; } let label = state_variable.ir_node.name.unparse().to_string(); - let type_name = self.semantic.type_internal_name(variable_type_id); + let type_name = self + .semantic + .type_internal_name(variable_type_id) + .to_owned(); storage_layout.push(StorageItem { node_id, label, @@ -121,8 +124,8 @@ impl ContractDefinitionStruct { // Ready slot and offset for the next variable byte_offset_in_slot += variable_size; - current_slot += U256::from(byte_offset_in_slot / SemanticContext::SLOT_SIZE); - byte_offset_in_slot %= SemanticContext::SLOT_SIZE; + current_slot += U256::from(byte_offset_in_slot / SLOT_SIZE); + byte_offset_in_slot %= SLOT_SIZE; } Some(storage_layout) } diff --git a/crates/solidity-v2/outputs/cargo/ast/src/abi/node_extensions/parameters.rs b/crates/solidity-v2/outputs/cargo/ast/src/abi/node_extensions/parameters.rs index ebc5381c27..4e0042cb51 100644 --- a/crates/solidity-v2/outputs/cargo/ast/src/abi/node_extensions/parameters.rs +++ b/crates/solidity-v2/outputs/cargo/ast/src/abi/node_extensions/parameters.rs @@ -39,7 +39,7 @@ impl ParametersStruct { pub(crate) fn compute_canonical_signature(&self) -> Option { let mut result = Vec::new(); for type_id in self.parameter_types_iter() { - result.push(self.semantic.type_canonical_name(type_id?)?); + result.push(self.semantic.type_canonical_name(type_id?)?.to_owned()); } Some(result.join(",")) } @@ -47,7 +47,7 @@ impl ParametersStruct { pub(crate) fn compute_internal_signature(&self) -> Option { let mut result = Vec::new(); for type_id in self.parameter_types_iter() { - result.push(self.semantic.type_internal_name(type_id?)); + result.push(self.semantic.type_internal_name(type_id?).to_owned()); } Some(result.join(",")) } diff --git a/crates/solidity-v2/outputs/cargo/ast/src/abi/node_extensions/state_variable_definition.rs b/crates/solidity-v2/outputs/cargo/ast/src/abi/node_extensions/state_variable_definition.rs index 4104c37363..59f282a867 100644 --- a/crates/solidity-v2/outputs/cargo/ast/src/abi/node_extensions/state_variable_definition.rs +++ b/crates/solidity-v2/outputs/cargo/ast/src/abi/node_extensions/state_variable_definition.rs @@ -73,7 +73,7 @@ impl StateVariableDefinitionStruct { let parameters = function_type .parameter_types .iter() - .map(|type_id| self.semantic.type_internal_name(*type_id)) + .map(|type_id| self.semantic.type_internal_name(*type_id).to_owned()) .collect::>() .join(","); Some(format!( diff --git a/crates/solidity-v2/outputs/cargo/ast/src/ast/node_extensions/contract_definition.rs b/crates/solidity-v2/outputs/cargo/ast/src/ast/node_extensions/contract_definition.rs index f82fc647e3..0e0e11b35a 100644 --- a/crates/solidity-v2/outputs/cargo/ast/src/ast/node_extensions/contract_definition.rs +++ b/crates/solidity-v2/outputs/cargo/ast/src/ast/node_extensions/contract_definition.rs @@ -1,5 +1,7 @@ -use std::cmp::Ordering; - +use super::super::nodes::{ + create_error_definition, create_event_definition, create_function_definition, + create_state_variable_definition, +}; use super::super::{ ContractDefinitionStruct, Definition, ErrorDefinition, EventDefinition, FunctionDefinition, FunctionKind, StateVariableDefinition, @@ -20,7 +22,7 @@ impl ContractDefinitionStruct { /// Returns the list of contracts/interfaces in the hierarchy (including /// self) in the order given by the C3 linearisation, with self contract /// always first - pub fn compute_linearised_bases(&self) -> Vec { + pub fn linearised_bases(&self) -> Vec { let Some(base_node_ids) = self .semantic .binder() @@ -47,16 +49,12 @@ impl ContractDefinitionStruct { } /// Returns the list of state variable definitions in the order laid out in storage - pub fn compute_linearised_state_variables(&self) -> Vec { - let mut state_variables = Vec::new(); - let bases = self.compute_linearised_bases(); - for base in bases.iter().rev() { - let ContractBase::Contract(contract) = base else { - continue; - }; - state_variables.extend(contract.members().iter_state_variable_definitions()); - } - state_variables + pub fn linearised_state_variables(&self) -> Vec { + self.semantic + .linearised_state_variables(self.ir_node.id()) + .iter() + .map(|ir_node| create_state_variable_definition(ir_node, &self.semantic)) + .collect() } pub fn functions(&self) -> Vec { @@ -86,82 +84,35 @@ impl ContractDefinitionStruct { /// Returns the list of functions defined in all the hierarchy of the /// contract, in alphabetical order - pub fn compute_linearised_functions(&self) -> Vec { - let mut functions: Vec = Vec::new(); - let bases = self.compute_linearised_bases(); - for base in &bases { - // TODO(validation) SDR[3]: we don't pick up functions defined in - // interfaces because they should be implemented in inheriting - // contracts, but this is not yet enforced anywhere - let ContractBase::Contract(contract) = base else { - continue; - }; - - // Handle function overriding - let contract_functions = contract - .functions() - .into_iter() - .filter(|function| { - // check the existing functions and remove any duplicates - // because they should be overridden by them - let existing = functions - .iter() - .any(|linearised_function| linearised_function.overrides(function)); - !existing - }) - // collect to avoid holding the read-borrow on `functions` - .collect::>(); - - functions.extend(contract_functions); - } - - // sort returned functions by name - functions.sort_by(|a, b| match (a.name(), b.name()) { - (None, None) => Ordering::Equal, - (None, Some(_)) => Ordering::Less, - (Some(_), None) => Ordering::Greater, - (Some(a), Some(b)) => a.name().cmp(&b.name()), - }); - functions + pub fn linearised_functions(&self) -> Vec { + self.semantic + .linearised_functions(self.ir_node.id()) + .iter() + .map(|ir_node| create_function_definition(ir_node, &self.semantic)) + .collect() } pub fn errors(&self) -> Vec { self.members().iter_error_definitions().collect() } - pub fn compute_linearised_errors(&self) -> Vec { - let mut errors = Vec::new(); - let bases = self.compute_linearised_bases(); - for base in bases.iter().rev() { - match base { - ContractBase::Contract(contract) => { - errors.extend(contract.members().iter_error_definitions()); - } - ContractBase::Interface(interface) => { - errors.extend(interface.members().iter_error_definitions()); - } - } - } - errors + pub fn linearised_errors(&self) -> Vec { + self.semantic + .linearised_errors(self.ir_node.id()) + .iter() + .map(|ir_node| create_error_definition(ir_node, &self.semantic)) + .collect() } pub fn events(&self) -> Vec { self.members().iter_event_definitions().collect() } - pub fn compute_linearised_events(&self) -> Vec { - let mut events = Vec::new(); - let bases = self.compute_linearised_bases(); - for base in bases.iter().rev() { - match base { - ContractBase::Contract(contract) => { - events.extend(contract.members().iter_event_definitions()); - } - ContractBase::Interface(interface) => { - events.extend(interface.members().iter_event_definitions()); - } - } - } - events + pub fn linearised_events(&self) -> Vec { + self.semantic + .linearised_events(self.ir_node.id()) + .iter() + .map(|ir_node| create_event_definition(ir_node, &self.semantic)) + .collect() } } diff --git a/crates/solidity-v2/outputs/cargo/ast/src/ast/node_extensions/function_definition.rs b/crates/solidity-v2/outputs/cargo/ast/src/ast/node_extensions/function_definition.rs deleted file mode 100644 index 11f1a1ef78..0000000000 --- a/crates/solidity-v2/outputs/cargo/ast/src/ast/node_extensions/function_definition.rs +++ /dev/null @@ -1,39 +0,0 @@ -use super::super::{FunctionDefinition, FunctionDefinitionStruct}; - -impl FunctionDefinitionStruct { - pub(crate) fn overrides(&self, other: &FunctionDefinition) -> bool { - let name_matches = match (&self.ir_node.name, &other.ir_node.name) { - (None, None) => { - // for unnamed functions, we check the kind because `receive` - // and `fallback` may have the same parameters but they are - // different functions - self.ir_node.kind == other.ir_node.kind - } - (Some(name), Some(other_name)) => name.unparse() == other_name.unparse(), - _ => false, - }; - if !name_matches { - return false; - } - let type_id = self - .semantic - .binder() - .node_typing(self.ir_node.id()) - .as_type_id(); - let other_type_id = self - .semantic - .binder() - .node_typing(other.ir_node.id()) - .as_type_id(); - - match (type_id, other_type_id) { - (Some(type_id), Some(other_type_id)) => self - .semantic - .types() - .type_id_is_function_and_overrides(type_id, other_type_id), - _ => false, - } - - // TODO(validation) SDR[6]: check also that the function mutability is stricter than other's - } -} diff --git a/crates/solidity-v2/outputs/cargo/ast/src/ast/node_extensions/interface_definition.rs b/crates/solidity-v2/outputs/cargo/ast/src/ast/node_extensions/interface_definition.rs deleted file mode 100644 index 9229d87ec6..0000000000 --- a/crates/solidity-v2/outputs/cargo/ast/src/ast/node_extensions/interface_definition.rs +++ /dev/null @@ -1,23 +0,0 @@ -use super::super::{ContractMember, ErrorDefinition, EventDefinition, InterfaceMembersStruct}; - -impl InterfaceMembersStruct { - pub(crate) fn iter_error_definitions(&self) -> impl Iterator + use<'_> { - self.iter().filter_map(|member| { - if let ContractMember::ErrorDefinition(error_definition) = member { - Some(error_definition) - } else { - None - } - }) - } - - pub(crate) fn iter_event_definitions(&self) -> impl Iterator + use<'_> { - self.iter().filter_map(|member| { - if let ContractMember::EventDefinition(event_definition) = member { - Some(event_definition) - } else { - None - } - }) - } -} diff --git a/crates/solidity-v2/outputs/cargo/ast/src/ast/node_extensions/mod.rs b/crates/solidity-v2/outputs/cargo/ast/src/ast/node_extensions/mod.rs index 59b273dc50..2f012d89b4 100644 --- a/crates/solidity-v2/outputs/cargo/ast/src/ast/node_extensions/mod.rs +++ b/crates/solidity-v2/outputs/cargo/ast/src/ast/node_extensions/mod.rs @@ -5,12 +5,10 @@ mod contract_definition; mod contract_members; mod decimal_number_expression; mod hex_number_expression; -mod interface_definition; pub use contract_base::ContractBase; mod expression; mod function_call_expression; -mod function_definition; mod identifier; mod identifier_path; mod source_unit; diff --git a/crates/solidity-v2/outputs/cargo/semantic/generated/public_api.txt b/crates/solidity-v2/outputs/cargo/semantic/generated/public_api.txt index e33efced66..77826b3201 100644 --- a/crates/solidity-v2/outputs/cargo/semantic/generated/public_api.txt +++ b/crates/solidity-v2/outputs/cargo/semantic/generated/public_api.txt @@ -261,20 +261,25 @@ impl core::marker::StructuralPartialEq for slang_solidity_v2_semantic::built_ins pub mod slang_solidity_v2_semantic::context pub struct slang_solidity_v2_semantic::context::SemanticContext impl slang_solidity_v2_semantic::context::SemanticContext -pub const slang_solidity_v2_semantic::context::SemanticContext::SLOT_SIZE: usize -pub fn slang_solidity_v2_semantic::context::SemanticContext::file_id_from_node_id(&self, node_id: slang_solidity_v2_common::nodes::NodeId) -> &str -pub fn slang_solidity_v2_semantic::context::SemanticContext::resolve_reference_identifier_to_definition_id(&self, node_id: slang_solidity_v2_common::nodes::NodeId) -> core::option::Option -pub fn slang_solidity_v2_semantic::context::SemanticContext::resolve_reference_identifier_to_immediate_definition_id(&self, node_id: slang_solidity_v2_common::nodes::NodeId) -> core::option::Option -pub fn slang_solidity_v2_semantic::context::SemanticContext::storage_size_of_type_id(&self, type_id: slang_solidity_v2_semantic::types::TypeId) -> core::option::Option -pub fn slang_solidity_v2_semantic::context::SemanticContext::type_canonical_name(&self, type_id: slang_solidity_v2_semantic::types::TypeId) -> core::option::Option -pub fn slang_solidity_v2_semantic::context::SemanticContext::type_internal_name(&self, type_id: slang_solidity_v2_semantic::types::TypeId) -> alloc::string::String -impl slang_solidity_v2_semantic::context::SemanticContext +pub fn slang_solidity_v2_semantic::context::SemanticContext::all_contracts(&self) -> impl core::iter::traits::iterator::Iterator + use<'_> pub fn slang_solidity_v2_semantic::context::SemanticContext::all_definitions(&self) -> impl core::iter::traits::iterator::Iterator + use<'_> pub fn slang_solidity_v2_semantic::context::SemanticContext::all_references(&self) -> impl core::iter::traits::iterator::Iterator + use<'_> pub fn slang_solidity_v2_semantic::context::SemanticContext::binder(&self) -> &slang_solidity_v2_semantic::binder::Binder pub fn slang_solidity_v2_semantic::context::SemanticContext::build_from(language_version: slang_solidity_v2_common::versions::language_versions::LanguageVersion, files: &[impl slang_solidity_v2_semantic::context::SemanticFile]) -> Self pub fn slang_solidity_v2_semantic::context::SemanticContext::find_contract_by_name(&self, name: &str) -> core::option::Option pub fn slang_solidity_v2_semantic::context::SemanticContext::types(&self) -> &slang_solidity_v2_semantic::types::TypeRegistry +impl slang_solidity_v2_semantic::context::SemanticContext +pub fn slang_solidity_v2_semantic::context::SemanticContext::file_id_from_node_id(&self, node_id: slang_solidity_v2_common::nodes::NodeId) -> &str +pub fn slang_solidity_v2_semantic::context::SemanticContext::linearised_errors(&self, contract_id: slang_solidity_v2_common::nodes::NodeId) -> &[slang_solidity_v2_ir::ir::nodes::ErrorDefinition] +pub fn slang_solidity_v2_semantic::context::SemanticContext::linearised_events(&self, contract_id: slang_solidity_v2_common::nodes::NodeId) -> &[slang_solidity_v2_ir::ir::nodes::EventDefinition] +pub fn slang_solidity_v2_semantic::context::SemanticContext::linearised_functions(&self, contract_id: slang_solidity_v2_common::nodes::NodeId) -> &[slang_solidity_v2_ir::ir::nodes::FunctionDefinition] +pub fn slang_solidity_v2_semantic::context::SemanticContext::linearised_state_variables(&self, contract_id: slang_solidity_v2_common::nodes::NodeId) -> &[slang_solidity_v2_ir::ir::nodes::StateVariableDefinition] +pub fn slang_solidity_v2_semantic::context::SemanticContext::resolve_reference_identifier_to_definition_id(&self, node_id: slang_solidity_v2_common::nodes::NodeId) -> core::option::Option +pub fn slang_solidity_v2_semantic::context::SemanticContext::resolve_reference_identifier_to_immediate_definition_id(&self, node_id: slang_solidity_v2_common::nodes::NodeId) -> core::option::Option +pub fn slang_solidity_v2_semantic::context::SemanticContext::storage_size_of_type_id(&self, type_id: slang_solidity_v2_semantic::types::TypeId) -> core::option::Option +pub fn slang_solidity_v2_semantic::context::SemanticContext::type_canonical_name(&self, type_id: slang_solidity_v2_semantic::types::TypeId) -> core::option::Option<&str> +pub fn slang_solidity_v2_semantic::context::SemanticContext::type_internal_name(&self, type_id: slang_solidity_v2_semantic::types::TypeId) -> &str +pub const slang_solidity_v2_semantic::context::SLOT_SIZE: usize pub trait slang_solidity_v2_semantic::context::SemanticFile pub fn slang_solidity_v2_semantic::context::SemanticFile::id(&self) -> &str pub fn slang_solidity_v2_semantic::context::SemanticFile::ir_root(&self) -> &slang_solidity_v2_ir::ir::nodes::SourceUnit diff --git a/crates/solidity-v2/outputs/cargo/semantic/src/context/contract_data_cache.rs b/crates/solidity-v2/outputs/cargo/semantic/src/context/contract_data_cache.rs new file mode 100644 index 0000000000..4f7c2a823d --- /dev/null +++ b/crates/solidity-v2/outputs/cargo/semantic/src/context/contract_data_cache.rs @@ -0,0 +1,245 @@ +use std::cmp::Ordering; +use std::collections::HashMap; +use std::sync::Arc; + +use slang_solidity_v2_common::nodes::NodeId; +use slang_solidity_v2_ir::ir; + +use crate::binder::{Binder, Definition}; +use crate::types::TypeRegistry; + +/// Pre-computed derived data for a single contract. +#[allow(clippy::struct_field_names)] +struct ContractData { + linearised_functions: Vec, + linearised_state_variables: Vec, + linearised_errors: Vec, + linearised_events: Vec, +} + +/// Cache of derived data about contracts, computed once at the end of the +/// semantic passes and stored on the `SemanticContext`. Every contract's +/// `NodeId` has an entry in `data`. +pub(super) struct ContractDataCache { + /// All contract definitions in this compilation unit, in registration + /// order (deterministic iteration for `all_contracts`). + contracts: Vec, + /// Lookup by simple name. Picks one definition arbitrarily if multiple + /// contracts share the same name across files. + contract_by_name: HashMap, + /// Per-contract derived data, keyed by contract `NodeId`. + data: HashMap, +} + +impl ContractDataCache { + pub(super) fn build_from(binder: &Binder, types: &TypeRegistry) -> Self { + let mut contracts = Vec::new(); + let mut contract_by_name = HashMap::new(); + let mut data = HashMap::new(); + + for (contract_id, definition) in binder.definitions() { + let Definition::Contract(contract) = definition else { + continue; + }; + let contract_id = *contract_id; + let ir_node = Arc::clone(&contract.ir_node); + let name = contract.ir_node.name.unparse().to_string(); + + let linearised_functions = compute_linearised_functions(binder, types, contract_id); + let linearised_state_variables = + collect_linearised_state_variables(binder, contract_id); + let linearised_errors = collect_linearised_errors(binder, contract_id); + let linearised_events = collect_linearised_events(binder, contract_id); + + data.insert( + contract_id, + ContractData { + linearised_functions, + linearised_state_variables, + linearised_errors, + linearised_events, + }, + ); + + contract_by_name.insert(name, Arc::clone(&ir_node)); + contracts.push(ir_node); + } + + Self { + contracts, + contract_by_name, + data, + } + } + + fn get(&self, contract_id: NodeId) -> &ContractData { + self.data + .get(&contract_id) + .expect("contract_id is a registered contract") + } + + pub(super) fn all_contracts(&self) -> impl Iterator { + self.contracts.iter() + } + + pub(super) fn find_contract_by_name(&self, name: &str) -> Option<&ir::ContractDefinition> { + self.contract_by_name.get(name) + } + + pub(super) fn linearised_functions(&self, contract_id: NodeId) -> &[ir::FunctionDefinition] { + &self.get(contract_id).linearised_functions + } + + pub(super) fn linearised_state_variables( + &self, + contract_id: NodeId, + ) -> &[ir::StateVariableDefinition] { + &self.get(contract_id).linearised_state_variables + } + + pub(super) fn linearised_errors(&self, contract_id: NodeId) -> &[ir::ErrorDefinition] { + &self.get(contract_id).linearised_errors + } + + pub(super) fn linearised_events(&self, contract_id: NodeId) -> &[ir::EventDefinition] { + &self.get(contract_id).linearised_events + } +} + +fn compute_linearised_functions( + binder: &Binder, + types: &TypeRegistry, + contract_id: NodeId, +) -> Vec { + let Some(linearised_bases) = binder.get_linearised_bases(contract_id) else { + return Vec::new(); + }; + + let mut functions: Vec = Vec::new(); + for base_id in linearised_bases { + // TODO(validation) SDR[3]: we don't pick up functions defined in + // interfaces because they should be implemented in inheriting contracts, + // but this is not yet enforced anywhere. + let Some(Definition::Contract(base)) = binder.find_definition_by_id(*base_id) else { + continue; + }; + + for member in &base.ir_node.members { + let ir::ContractMember::FunctionDefinition(function) = member else { + continue; + }; + if !matches!( + function.kind, + ir::FunctionKind::Regular | ir::FunctionKind::Fallback | ir::FunctionKind::Receive + ) { + continue; + } + let overridden = functions + .iter() + .any(|existing| function_overrides(binder, types, existing, function)); + if !overridden { + functions.push(Arc::clone(function)); + } + } + } + + functions.sort_by(|a, b| match (&a.name, &b.name) { + (None, None) => Ordering::Equal, + (None, Some(_)) => Ordering::Less, + (Some(_), None) => Ordering::Greater, + (Some(a), Some(b)) => a.unparse().cmp(b.unparse()), + }); + functions +} + +fn function_overrides( + binder: &Binder, + types: &TypeRegistry, + function: &ir::FunctionDefinition, + other: &ir::FunctionDefinition, +) -> bool { + let name_matches = match (&function.name, &other.name) { + (None, None) => function.kind == other.kind, + (Some(name), Some(other_name)) => name.unparse() == other_name.unparse(), + _ => false, + }; + if !name_matches { + return false; + } + let type_id = binder.node_typing(function.id()).as_type_id(); + let other_type_id = binder.node_typing(other.id()).as_type_id(); + match (type_id, other_type_id) { + (Some(type_id), Some(other_type_id)) => { + types.type_id_is_function_and_overrides(type_id, other_type_id) + } + _ => false, + } +} + +/// Walks the linearised bases in reverse (most-base first) and concatenates +/// every contract's state-variable members in source order. Interfaces don't +/// contribute state variables in Solidity. +fn collect_linearised_state_variables( + binder: &Binder, + contract_id: NodeId, +) -> Vec { + let mut state_variables = Vec::new(); + let Some(linearised_bases) = binder.get_linearised_bases(contract_id) else { + return state_variables; + }; + for base_id in linearised_bases.iter().rev() { + let Some(Definition::Contract(base)) = binder.find_definition_by_id(*base_id) else { + continue; + }; + for member in &base.ir_node.members { + if let ir::ContractMember::StateVariableDefinition(state_variable) = member { + state_variables.push(Arc::clone(state_variable)); + } + } + } + state_variables +} + +/// Walks the linearised bases in reverse and concatenates every contract / +/// interface error definition. +fn collect_linearised_errors(binder: &Binder, contract_id: NodeId) -> Vec { + let mut errors = Vec::new(); + let Some(linearised_bases) = binder.get_linearised_bases(contract_id) else { + return errors; + }; + for base_id in linearised_bases.iter().rev() { + let members = match binder.find_definition_by_id(*base_id) { + Some(Definition::Contract(base)) => &base.ir_node.members, + Some(Definition::Interface(base)) => &base.ir_node.members, + _ => continue, + }; + for member in members { + if let ir::ContractMember::ErrorDefinition(error) = member { + errors.push(Arc::clone(error)); + } + } + } + errors +} + +/// Walks the linearised bases in reverse and concatenates every contract / +/// interface event definition. +fn collect_linearised_events(binder: &Binder, contract_id: NodeId) -> Vec { + let mut events = Vec::new(); + let Some(linearised_bases) = binder.get_linearised_bases(contract_id) else { + return events; + }; + for base_id in linearised_bases.iter().rev() { + let members = match binder.find_definition_by_id(*base_id) { + Some(Definition::Contract(base)) => &base.ir_node.members, + Some(Definition::Interface(base)) => &base.ir_node.members, + _ => continue, + }; + for member in members { + if let ir::ContractMember::EventDefinition(event) = member { + events.push(Arc::clone(event)); + } + } + } + events +} diff --git a/crates/solidity-v2/outputs/cargo/semantic/src/context/mod.rs b/crates/solidity-v2/outputs/cargo/semantic/src/context/mod.rs index ef928a736f..c7118dfd86 100644 --- a/crates/solidity-v2/outputs/cargo/semantic/src/context/mod.rs +++ b/crates/solidity-v2/outputs/cargo/semantic/src/context/mod.rs @@ -1,19 +1,24 @@ -use std::collections::HashSet; -use std::sync::Arc; - +use contract_data_cache::ContractDataCache; use file_node_mapper::FileNodeMapper; use slang_solidity_v2_common::nodes::NodeId; use slang_solidity_v2_common::utils::strip_string_literal_quotes; use slang_solidity_v2_common::versions::LanguageVersion; use slang_solidity_v2_ir::ir; +use type_data_cache::TypeDataCache; use crate::binder::{Binder, Definition, Reference}; use crate::passes::{ p1_collect_definitions, p2_linearise_contracts, p3_type_definitions, p4_resolve_references, }; -use crate::types::{Type, TypeId, TypeRegistry}; +use crate::types::{TypeId, TypeRegistry}; +mod contract_data_cache; mod file_node_mapper; +mod type_data_cache; + +pub const SLOT_SIZE: usize = 32; +pub(crate) const ADDRESS_BYTE_SIZE: usize = 20; +pub(crate) const SELECTOR_SIZE: usize = 4; /// Trait for files that can be used as input to the semantic analysis passes. pub trait SemanticFile { @@ -55,6 +60,8 @@ pub struct SemanticContext { binder: Binder, types: TypeRegistry, file_node_mapper: FileNodeMapper, + type_data: TypeDataCache, + contract_data: ContractDataCache, } impl SemanticContext { @@ -68,11 +75,15 @@ impl SemanticContext { p4_resolve_references::run(files, &mut binder, &mut types, language_version); let file_node_mapper = FileNodeMapper::build_from(files); + let type_data = TypeDataCache::build_from(&binder, &types); + let contract_data = ContractDataCache::build_from(&binder, &types); Self { binder, types, file_node_mapper, + type_data, + contract_data, } } @@ -94,17 +105,14 @@ impl SemanticContext { self.binder.references().values() } + /// Iterates over every contract definition in this compilation unit, in + /// registration order. + pub fn all_contracts(&self) -> impl Iterator + use<'_> { + self.contract_data.all_contracts() + } + pub fn find_contract_by_name(&self, name: &str) -> Option { - self.binder().definitions().values().find_map(|definition| { - let Definition::Contract(contract) = definition else { - return None; - }; - if definition.identifier().unparse() == name { - Some(Arc::clone(&contract.ir_node)) - } else { - None - } - }) + self.contract_data.find_contract_by_name(name).cloned() } } @@ -113,6 +121,40 @@ impl SemanticContext { self.file_node_mapper.file_id_from_node_id(node_id) } + /// Returns the pre-computed list of functions visible in the given + /// contract's hierarchy (per C3 linearisation), with overrides resolved and + /// sorted by name. `contract_id` must be a registered contract definition. + pub fn linearised_functions(&self, contract_id: NodeId) -> &[ir::FunctionDefinition] { + self.contract_data.linearised_functions(contract_id) + } + + /// Returns the pre-computed list of state variables visible in the given + /// contract's hierarchy, in storage-layout order (most-base first, then + /// each contract's own variables). `contract_id` must be a registered + /// contract definition. + pub fn linearised_state_variables( + &self, + contract_id: NodeId, + ) -> &[ir::StateVariableDefinition] { + self.contract_data.linearised_state_variables(contract_id) + } + + /// Returns the pre-computed list of errors visible in the given contract's + /// hierarchy (including base contracts and interfaces, in reverse + /// linearisation order). `contract_id` must be a registered contract + /// definition. + pub fn linearised_errors(&self, contract_id: NodeId) -> &[ir::ErrorDefinition] { + self.contract_data.linearised_errors(contract_id) + } + + /// Returns the pre-computed list of events visible in the given contract's + /// hierarchy (including base contracts and interfaces, in reverse + /// linearisation order). `contract_id` must be a registered contract + /// definition. + pub fn linearised_events(&self, contract_id: NodeId) -> &[ir::EventDefinition] { + self.contract_data.linearised_events(contract_id) + } + pub fn resolve_reference_identifier_to_definition_id(&self, node_id: NodeId) -> Option { let reference = self .binder() @@ -132,247 +174,22 @@ impl SemanticContext { reference.resolution.as_definition_id() } - pub(crate) fn definition_canonical_name(&self, definition_id: NodeId) -> String { - self.binder - .find_definition_by_id(definition_id) - .unwrap() - .identifier() - .unparse() - .to_string() - } - - pub fn type_internal_name(&self, type_id: TypeId) -> String { - match self.types.get_type_by_id(type_id) { - Type::Address { .. } => "address".to_string(), - Type::Array { element_type, .. } => { - format!( - "{element}[]", - element = self.type_internal_name(*element_type) - ) - } - Type::Boolean => "bool".to_string(), - Type::ByteArray { width } => format!("bytes{width}"), - Type::Bytes { .. } => "bytes".to_string(), - Type::FixedPointNumber { - signed, - bits, - precision_bits, - } => format!( - "{prefix}{bits}x{precision_bits}", - prefix = if *signed { "fixed" } else { "ufixed" }, - ), - Type::FixedSizeArray { - element_type, size, .. - } => { - format!( - "{element}[{size}]", - element = self.type_internal_name(*element_type), - ) - } - Type::Function(_) => "function".to_string(), - Type::Integer { signed, bits } => format!( - "{prefix}{bits}", - prefix = if *signed { "int" } else { "uint" } - ), - Type::Literal(_) => "literal".to_string(), - Type::Mapping { - key_type_id, - value_type_id, - } => format!( - "mapping({key_type} => {value_type})", - key_type = self.type_internal_name(*key_type_id), - value_type = self.type_internal_name(*value_type_id) - ), - Type::String { .. } => "string".to_string(), - Type::Tuple { types } => format!( - "({types})", - types = types - .iter() - .map(|type_id| self.type_internal_name(*type_id)) - .collect::>() - .join(",") - ), - Type::Contract { definition_id } - | Type::Enum { definition_id } - | Type::Interface { definition_id } - | Type::Struct { definition_id, .. } - | Type::UserDefinedValue { definition_id } => { - self.definition_canonical_name(*definition_id) - } - Type::Void => "void".to_string(), - } - } - - pub fn type_canonical_name(&self, type_id: TypeId) -> Option { - self.type_canonical_name_impl(type_id, &mut HashSet::new()) + /// Returns the internal (debug-ish) name for a type. The cache is built + /// for every `TypeId` registered in the type registry, so this never fails + /// for a `TypeId` obtained from this context. + pub fn type_internal_name(&self, type_id: TypeId) -> &str { + self.type_data.internal_name(type_id) } - fn type_canonical_name_impl( - &self, - type_id: TypeId, - visited_structs: &mut HashSet, - ) -> Option { - match self.types.get_type_by_id(type_id) { - Type::Address { .. } - | Type::Boolean - | Type::ByteArray { .. } - | Type::Bytes { .. } - | Type::FixedPointNumber { .. } - | Type::Function(_) - | Type::Integer { .. } - | Type::String { .. } => Some(self.type_internal_name(type_id)), - - Type::Array { element_type, .. } => self - .type_canonical_name_impl(*element_type, visited_structs) - .map(|element| format!("{element}[]",)), - Type::FixedSizeArray { - element_type, size, .. - } => self - .type_canonical_name_impl(*element_type, visited_structs) - .map(|element| format!("{element}[{size}]",)), - - Type::Contract { .. } | Type::Interface { .. } => Some("address".to_string()), - - Type::Enum { .. } => Some("uint8".to_string()), - - Type::Struct { definition_id, .. } => { - // Recursive structs are not valid Solidity, but guard against cycles - // to avoid unbounded recursion if malformed types reach this point. - // TODO(validation) SDR[19]: The recursion should be detected in the - // `type_definition` pass. - if !visited_structs.insert(*definition_id) { - return None; - } - let Definition::Struct(struct_) = self - .binder - .find_definition_by_id(*definition_id) - .expect("definition in type exists") - else { - unreachable!("definition in struct type is not a struct"); - }; - let mut fields = Vec::new(); - for member in &struct_.ir_node.members { - let member_type_id = self.binder.node_typing(member.id()).as_type_id()?; - let field_type = - self.type_canonical_name_impl(member_type_id, visited_structs)?; - fields.push(field_type); - } - visited_structs.remove(definition_id); - Some(format!("({fields})", fields = fields.join(","))) - } - - Type::UserDefinedValue { definition_id } => { - let Definition::UserDefinedValueType(udvt) = self - .binder - .find_definition_by_id(*definition_id) - .expect("definition in type exists") - else { - unreachable!("definition in user defined value type is not a UDVT"); - }; - udvt.target_type_id - .and_then(|type_id| self.type_canonical_name_impl(type_id, visited_structs)) - } - - Type::Literal(_) | Type::Mapping { .. } | Type::Tuple { .. } | Type::Void => None, - } + /// Returns the canonical (ABI-encoded) name for a type, or `None` if the + /// type has no canonical form (mappings, tuples, recursive structs, ...). + pub fn type_canonical_name(&self, type_id: TypeId) -> Option<&str> { + self.type_data.canonical_name(type_id) } - pub const SLOT_SIZE: usize = 32; - pub(crate) const ADDRESS_BYTE_SIZE: usize = 20; - pub(crate) const SELECTOR_SIZE: usize = 4; - + /// Returns the storage byte size for a type, or `None` if the type doesn't + /// live in storage (literals, tuples, void). pub fn storage_size_of_type_id(&self, type_id: TypeId) -> Option { - self.storage_size_of_type_id_impl(type_id, &mut HashSet::new()) - } - - fn storage_size_of_type_id_impl( - &self, - type_id: TypeId, - visited_structs: &mut HashSet, - ) -> Option { - match self.types.get_type_by_id(type_id) { - Type::Address { .. } | Type::Contract { .. } | Type::Interface { .. } => { - Some(Self::ADDRESS_BYTE_SIZE) - } - Type::Boolean => Some(1), - Type::FixedPointNumber { bits, .. } | Type::Integer { bits, .. } => { - Some((bits.div_ceil(8)).try_into().unwrap()) - } - Type::ByteArray { width } => Some((*width).try_into().unwrap()), - Type::Enum { .. } => Some(1), - Type::Bytes { .. } | Type::String { .. } => Some(Self::SLOT_SIZE), - Type::Mapping { .. } => Some(Self::SLOT_SIZE), - - Type::Array { .. } => Some(Self::SLOT_SIZE), - Type::FixedSizeArray { - element_type, size, .. - } => { - let element_size = - self.storage_size_of_type_id_impl(*element_type, visited_structs)?; - if element_size > Self::SLOT_SIZE { - let slots_per_element = element_size.div_ceil(Self::SLOT_SIZE); - Some(slots_per_element * size * Self::SLOT_SIZE) - } else { - let elements_per_slot = Self::SLOT_SIZE / element_size; - let num_slots = size.div_ceil(elements_per_slot); - Some(num_slots * Self::SLOT_SIZE) - } - } - - Type::Function(function_type) => { - if function_type.is_externally_visible() { - Some(Self::ADDRESS_BYTE_SIZE + Self::SELECTOR_SIZE) - } else { - // NOTE: an internal function ref type is 8 bytes long, it's - // opaque and its meaning not documented - Some(8) - } - } - Type::Struct { definition_id, .. } => { - // Recursive structs are not valid Solidity, but guard against cycles - // to avoid unbounded recursion if malformed types reach this point. - // TODO(validation) SDR[19]: The recursion should be detected in the - // `type_definition` pass. - if !visited_structs.insert(*definition_id) { - return None; - } - let Definition::Struct(struct_definition) = - self.binder.find_definition_by_id(*definition_id)? - else { - return None; - }; - let mut ptr: usize = 0; - for member in &struct_definition.ir_node.members { - let member_type_id = self.binder.node_typing(member.id()).as_type_id()?; - let member_size = - self.storage_size_of_type_id_impl(member_type_id, visited_structs)?; - let remaining_bytes = Self::SLOT_SIZE - (ptr % Self::SLOT_SIZE); - if remaining_bytes < Self::SLOT_SIZE && member_size >= remaining_bytes { - ptr += remaining_bytes; - } - ptr += member_size; - } - visited_structs.remove(definition_id); - // round up the final allocation to a full slot, because the - // next variable needs to start at the next slot anyway - ptr = ptr.div_ceil(Self::SLOT_SIZE) * Self::SLOT_SIZE; - Some(ptr) - } - Type::UserDefinedValue { definition_id } => { - let Definition::UserDefinedValueType(user_defined_value) = - self.binder.find_definition_by_id(*definition_id)? - else { - return None; - }; - self.storage_size_of_type_id_impl( - user_defined_value.target_type_id?, - visited_structs, - ) - } - - Type::Literal(_) => None, - Type::Tuple { .. } => None, - Type::Void => None, - } + self.type_data.size_in_storage(type_id) } } diff --git a/crates/solidity-v2/outputs/cargo/semantic/src/context/type_data_cache.rs b/crates/solidity-v2/outputs/cargo/semantic/src/context/type_data_cache.rs new file mode 100644 index 0000000000..c95fe3616a --- /dev/null +++ b/crates/solidity-v2/outputs/cargo/semantic/src/context/type_data_cache.rs @@ -0,0 +1,388 @@ +use std::collections::HashMap; + +use slang_solidity_v2_common::nodes::NodeId; + +use crate::binder::{Binder, Definition}; +use crate::context::{ADDRESS_BYTE_SIZE, SELECTOR_SIZE, SLOT_SIZE}; +use crate::types::{Type, TypeId, TypeRegistry}; + +/// Pre-computed derivations for a single registered type. +struct TypeData { + /// `type_internal_name` result. + internal_name: String, + + /// `type_canonical_name` result. `None` is a valid outcome (e.g. mappings, + /// recursive structs). + canonical_name: Option, + + /// `storage_size_of_type_id` result. `None` signals a type that doesn't + /// live in storage (literals, void, tuples). + size_in_storage: Option, +} + +/// Cache of type-derivation results, computed once at the end of the semantic +/// passes. Every `TypeId` registered in the `TypeRegistry` has an entry. +pub(super) struct TypeDataCache { + data: HashMap, +} + +impl TypeDataCache { + pub(super) fn build_from(binder: &Binder, types: &TypeRegistry) -> Self { + TypeDataCacheBuilder::new(binder, types).build() + } + + fn get(&self, type_id: TypeId) -> &TypeData { + self.data + .get(&type_id) + .expect("type_id is registered in the type registry") + } + + pub(super) fn internal_name(&self, type_id: TypeId) -> &str { + &self.get(type_id).internal_name + } + + pub(super) fn canonical_name(&self, type_id: TypeId) -> Option<&str> { + self.get(type_id).canonical_name.as_deref() + } + + pub(super) fn size_in_storage(&self, type_id: TypeId) -> Option { + self.get(type_id).size_in_storage + } +} + +/// Builds the per-type derivation cache in a single structural traversal of +/// the type registry. Sub-type derivations are resolved by looking them up in +/// the (partially built) cache, so each type is visited at most once. +struct TypeDataCacheBuilder<'a> { + binder: &'a Binder, + types: &'a TypeRegistry, + data: HashMap, +} + +impl<'a> TypeDataCacheBuilder<'a> { + fn new(binder: &'a Binder, types: &'a TypeRegistry) -> Self { + Self { + binder, + types, + data: HashMap::new(), + } + } + + fn build(mut self) -> TypeDataCache { + for (type_id, _) in self.types.iter_types() { + self.compute_type_data(type_id); + } + TypeDataCache { data: self.data } + } + + fn definition_canonical_name(&self, definition_id: NodeId) -> String { + self.binder + .find_definition_by_id(definition_id) + .unwrap() + .identifier() + .unparse() + .to_string() + } + + /// Populates the cache for `type_id`. Sub-type derivations are resolved by + /// ensuring the sub-type is computed (recursive call), then looking up the + /// cached values — there are no separate recursive paths per derivation. + /// + /// Cycle detection (only relevant for self-referential structs, which are + /// invalid Solidity but guarded against defensively): a placeholder entry + /// (with `internal_name` set and `canonical_name`/`storage_size` left as + /// `None`) is inserted at the top of the `Struct` arm before recursing + /// into members, so a recursive `compute(self_type)` short-circuits via + /// the `data.contains_key` check. The cycled struct's canonical/storage + /// are still `None` at that point, so the sub-type lookup inside + /// `struct_canonical_name` / `struct_storage_size` returns `None`, which + /// propagates up. + #[allow(clippy::too_many_lines)] + fn compute_type_data(&mut self, type_id: TypeId) { + if self.data.contains_key(&type_id) { + return; + } + match self.types.get_type_by_id(type_id) { + Type::Address { .. } => self.insert( + type_id, + "address".to_string(), + Some("address".to_string()), + Some(ADDRESS_BYTE_SIZE), + ), + Type::Boolean => self.insert( + type_id, + "bool".to_string(), + Some("bool".to_string()), + Some(1), + ), + Type::ByteArray { width } => { + let width = *width; + let name = format!("bytes{width}"); + let storage = width.try_into().unwrap(); + self.insert(type_id, name.clone(), Some(name), Some(storage)); + } + Type::Bytes { .. } => self.insert( + type_id, + "bytes".to_string(), + Some("bytes".to_string()), + Some(SLOT_SIZE), + ), + Type::FixedPointNumber { + signed, + bits, + precision_bits, + } => { + let (signed, bits, precision_bits) = (*signed, *bits, *precision_bits); + let name = format!( + "{prefix}{bits}x{precision_bits}", + prefix = if signed { "fixed" } else { "ufixed" } + ); + let storage = (bits.div_ceil(8)).try_into().unwrap(); + self.insert(type_id, name.clone(), Some(name), Some(storage)); + } + Type::Function(function_type) => { + let externally_visible = function_type.is_externally_visible(); + // NOTE: an internal function ref type is 8 bytes long, it's + // opaque and its meaning not documented + let storage = if externally_visible { + ADDRESS_BYTE_SIZE + SELECTOR_SIZE + } else { + 8 + }; + self.insert( + type_id, + "function".to_string(), + Some("function".to_string()), + Some(storage), + ); + } + Type::Integer { signed, bits } => { + let (signed, bits) = (*signed, *bits); + let name = format!( + "{prefix}{bits}", + prefix = if signed { "int" } else { "uint" } + ); + let storage = (bits.div_ceil(8)).try_into().unwrap(); + self.insert(type_id, name.clone(), Some(name), Some(storage)); + } + Type::Literal(kind) => { + let name = match kind { + crate::types::LiteralKind::Integer { value } + | crate::types::LiteralKind::HexInteger { value, .. } => { + format!("int_const {value}") + } + crate::types::LiteralKind::Rational { value } => { + format!( + "rational {numer}/{denom}", + numer = value.numer(), + denom = value.denom() + ) + } + crate::types::LiteralKind::HexString { .. } + | crate::types::LiteralKind::String { .. } => "literal_string".to_string(), + crate::types::LiteralKind::Address => "address_const".to_string(), + }; + self.insert(type_id, name, None, None); + } + Type::String { .. } => self.insert( + type_id, + "string".to_string(), + Some("string".to_string()), + Some(SLOT_SIZE), + ), + Type::Void => { + self.insert(type_id, "void".to_string(), None, None); + } + + Type::Array { element_type, .. } => { + let element_type = *element_type; + self.compute_type_data(element_type); + let element = &self.data[&element_type]; + let element_internal = element.internal_name.clone(); + let element_canonical = element.canonical_name.clone(); + self.insert( + type_id, + format!("{element_internal}[]"), + element_canonical.map(|c| format!("{c}[]")), + Some(SLOT_SIZE), + ); + } + Type::FixedSizeArray { + element_type, size, .. + } => { + let element_type = *element_type; + let size = *size; + self.compute_type_data(element_type); + let element = &self.data[&element_type]; + let element_internal = element.internal_name.clone(); + let element_canonical = element.canonical_name.clone(); + let element_size = element.size_in_storage; + let storage = element_size.map(|es| { + if es > SLOT_SIZE { + let slots_per_element = es.div_ceil(SLOT_SIZE); + slots_per_element * size * SLOT_SIZE + } else { + let elements_per_slot = SLOT_SIZE / es; + let num_slots = size.div_ceil(elements_per_slot); + num_slots * SLOT_SIZE + } + }); + self.insert( + type_id, + format!("{element_internal}[{size}]"), + element_canonical.map(|c| format!("{c}[{size}]")), + storage, + ); + } + Type::Mapping { + key_type_id, + value_type_id, + } => { + let key_type_id = *key_type_id; + let value_type_id = *value_type_id; + self.compute_type_data(key_type_id); + self.compute_type_data(value_type_id); + let key_internal = self.data[&key_type_id].internal_name.clone(); + let value_internal = self.data[&value_type_id].internal_name.clone(); + self.insert( + type_id, + format!("mapping({key_internal} => {value_internal})"), + None, + Some(SLOT_SIZE), + ); + } + Type::Tuple { types } => { + for element_type_id in types { + self.compute_type_data(*element_type_id); + } + let inner = types + .iter() + .map(|element_type_id| self.data[element_type_id].internal_name.clone()) + .collect::>() + .join(","); + self.insert(type_id, format!("({inner})"), None, None); + } + Type::Contract { definition_id } | Type::Interface { definition_id } => { + let definition_id = *definition_id; + let name = self.definition_canonical_name(definition_id); + self.insert( + type_id, + name, + Some("address".to_string()), + Some(ADDRESS_BYTE_SIZE), + ); + } + Type::Enum { definition_id } => { + let definition_id = *definition_id; + let name = self.definition_canonical_name(definition_id); + self.insert(type_id, name, Some("uint8".to_string()), Some(1)); + } + Type::Struct { definition_id, .. } => { + let definition_id = *definition_id; + // Insert a placeholder entry first so a recursive + // `compute_type_data()` (from a self-referential struct member) + // short-circuits at the top. canonical/storage are still `None` + // at that point, so the helpers below see `None` and break the + // cycle. + let internal = self.definition_canonical_name(definition_id); + self.insert(type_id, internal, None, None); + + if let Some(member_type_ids) = self.compute_struct_member_type_ids(definition_id) { + let canonical = self.struct_canonical_name(&member_type_ids); + let storage = self.struct_storage_size(&member_type_ids); + let entry = self.data.get_mut(&type_id).unwrap(); + entry.canonical_name = canonical; + entry.size_in_storage = storage; + } + } + Type::UserDefinedValue { definition_id } => { + let definition_id = *definition_id; + let internal = self.definition_canonical_name(definition_id); + let target_type_id = { + let Definition::UserDefinedValueType(udvt) = self + .binder + .find_definition_by_id(definition_id) + .expect("definition in type exists") + else { + unreachable!("definition in user defined value type is not a UDVT"); + }; + udvt.target_type_id + }; + let (canonical, storage) = if let Some(target_type_id) = target_type_id { + self.compute_type_data(target_type_id); + let target = &self.data[&target_type_id]; + (target.canonical_name.clone(), target.size_in_storage) + } else { + (None, None) + }; + self.insert(type_id, internal, canonical, storage); + } + } + } + + fn insert( + &mut self, + type_id: TypeId, + internal_name: String, + canonical_name: Option, + storage_size: Option, + ) { + self.data.insert( + type_id, + TypeData { + internal_name, + canonical_name, + size_in_storage: storage_size, + }, + ); + } + + fn struct_canonical_name(&mut self, member_type_ids: &[TypeId]) -> Option { + let mut fields = Vec::new(); + for member_type_id in member_type_ids { + // `None` here is either "the member's canonical name itself is + // None" (mapping, tuple, ...) or "we're in a cycle and the member's + // canonical entry is still the placeholder". Either way the parent + // struct has no canonical name. + let field_canonical = self.data[member_type_id].canonical_name.clone()?; + fields.push(field_canonical); + } + Some(format!("({})", fields.join(","))) + } + + fn struct_storage_size(&mut self, member_type_ids: &[TypeId]) -> Option { + let mut ptr: usize = 0; + for member_type_id in member_type_ids { + // Same cycle/no-storage handling as canonical above. + let member_size = self.data[member_type_id].size_in_storage?; + let remaining_bytes = SLOT_SIZE - (ptr % SLOT_SIZE); + if remaining_bytes < SLOT_SIZE && member_size >= remaining_bytes { + ptr += remaining_bytes; + } + ptr += member_size; + } + // round up the final allocation to a full slot, because the next variable + // needs to start at the next slot anyway + Some(ptr.div_ceil(SLOT_SIZE) * SLOT_SIZE) + } + + /// Recurse into a struct members and compute the data of their types, + /// returning the types. Returns `None` if the type of any member cannot be + /// resolved. + fn compute_struct_member_type_ids(&mut self, definition_id: NodeId) -> Option> { + let Definition::Struct(struct_) = self + .binder + .find_definition_by_id(definition_id) + .expect("definition in type exists") + else { + unreachable!("definition in struct type is not a struct"); + }; + let mut member_type_ids = Vec::new(); + for member in &struct_.ir_node.members { + let member_type_id = self.binder.node_typing(member.id()).as_type_id()?; + self.compute_type_data(member_type_id); + member_type_ids.push(member_type_id); + } + Some(member_type_ids) + } +} diff --git a/crates/solidity-v2/outputs/cargo/semantic/src/types/registry.rs b/crates/solidity-v2/outputs/cargo/semantic/src/types/registry.rs index 54506d8e35..98e2cd8142 100644 --- a/crates/solidity-v2/outputs/cargo/semantic/src/types/registry.rs +++ b/crates/solidity-v2/outputs/cargo/semantic/src/types/registry.rs @@ -806,7 +806,6 @@ impl TypeRegistry { } } -#[cfg(test)] impl TypeRegistry { pub(crate) fn iter_types(&self) -> impl Iterator { (0usize..).map(TypeId).zip(self.types.iter()) diff --git a/crates/solidity-v2/outputs/cargo/slang_solidity/generated/public_api.txt b/crates/solidity-v2/outputs/cargo/slang_solidity/generated/public_api.txt index 06c0a043cb..12550d42bc 100644 --- a/crates/solidity-v2/outputs/cargo/slang_solidity/generated/public_api.txt +++ b/crates/solidity-v2/outputs/cargo/slang_solidity/generated/public_api.txt @@ -20,6 +20,7 @@ pub fn slang_solidity_v2::compilation::CompilationBuilder::build(self) -> sla pub fn slang_solidity_v2::compilation::CompilationBuilder::create(language_version: slang_solidity_v2_common::versions::language_versions::LanguageVersion, config: C) -> slang_solidity_v2::compilation::CompilationBuilder pub struct slang_solidity_v2::compilation::CompilationUnit impl slang_solidity_v2::compilation::CompilationUnit +pub fn slang_solidity_v2::compilation::CompilationUnit::all_contracts(&self) -> impl core::iter::traits::iterator::Iterator + use<'_> pub fn slang_solidity_v2::compilation::CompilationUnit::all_definitions(&self) -> impl core::iter::traits::iterator::Iterator + use<'_> pub fn slang_solidity_v2::compilation::CompilationUnit::all_references(&self) -> impl core::iter::traits::iterator::Iterator + use<'_> pub fn slang_solidity_v2::compilation::CompilationUnit::compute_contracts_abi(&self) -> alloc::vec::Vec diff --git a/crates/solidity-v2/outputs/cargo/slang_solidity/src/compilation/unit.rs b/crates/solidity-v2/outputs/cargo/slang_solidity/src/compilation/unit.rs index 8593cdc3a4..0a6bb2c2a7 100644 --- a/crates/solidity-v2/outputs/cargo/slang_solidity/src/compilation/unit.rs +++ b/crates/solidity-v2/outputs/cargo/slang_solidity/src/compilation/unit.rs @@ -86,6 +86,14 @@ impl CompilationUnit { .map(|contract| ast::create_contract_definition(&contract, &self.semantic)) } + /// Iterates over every contract definition in this compilation unit, in + /// registration order. + pub fn all_contracts(&self) -> impl Iterator + use<'_> { + self.semantic + .all_contracts() + .map(|contract| ast::create_contract_definition(contract, &self.semantic)) + } + pub fn compute_contracts_abi(&self) -> Vec { self.files .values() diff --git a/crates/solidity-v2/outputs/cargo/slang_solidity/src/tests/abi.rs b/crates/solidity-v2/outputs/cargo/slang_solidity/src/tests/abi.rs index ddf5f2fe09..9df928a697 100644 --- a/crates/solidity-v2/outputs/cargo/slang_solidity/src/tests/abi.rs +++ b/crates/solidity-v2/outputs/cargo/slang_solidity/src/tests/abi.rs @@ -155,7 +155,7 @@ fn test_function_selector() { .find_contract_by_name("Counter") .expect("contract can be found"); - let functions = counter.compute_linearised_functions(); + let functions = counter.linearised_functions(); assert_eq!(functions.len(), 5); // all the functions in the contract are public @@ -165,7 +165,7 @@ fn test_function_selector() { assert_eq!(functions[3].compute_selector(), Some(0x7cf5_dab0_u32)); // increment(uint256) assert_eq!(functions[4].compute_selector(), Some(0x6aa6_33b6_u32)); // isEnabled() - let state_variables = counter.compute_linearised_state_variables(); + let state_variables = counter.linearised_state_variables(); assert_eq!(state_variables.len(), 4); // for state variables, selectors only make sense for public getters diff --git a/crates/solidity-v2/outputs/cargo/slang_solidity/src/tests/ast.rs b/crates/solidity-v2/outputs/cargo/slang_solidity/src/tests/ast.rs index 5ea803744b..38a9e1ab56 100644 --- a/crates/solidity-v2/outputs/cargo/slang_solidity/src/tests/ast.rs +++ b/crates/solidity-v2/outputs/cargo/slang_solidity/src/tests/ast.rs @@ -239,13 +239,13 @@ fn test_contract_direct_bases() { } #[test] -fn test_contract_compute_linearised_bases() { +fn test_contract_linearised_bases() { let unit = fixtures::Counter::build_compilation_unit(); let counter = unit .find_contract_by_name("Counter") .expect("can find Counter contract"); - let bases = counter.compute_linearised_bases(); + let bases = counter.linearised_bases(); assert_eq!(bases.len(), 3); let ContractBase::Contract(counter) = &bases[0] else { @@ -307,14 +307,14 @@ fn test_definition_references() { } #[test] -fn test_contract_compute_linearised_state_variables() { +fn test_contract_linearised_state_variables() { let unit = fixtures::Counter::build_compilation_unit(); let counter = unit .find_contract_by_name("Counter") .expect("can find Counter contract"); - let state_variables = counter.compute_linearised_state_variables(); + let state_variables = counter.linearised_state_variables(); assert_eq!(state_variables.len(), 4); assert_eq!(state_variables[0].name().name(), "_owner"); @@ -324,14 +324,14 @@ fn test_contract_compute_linearised_state_variables() { } #[test] -fn test_contract_compute_linearised_functions() { +fn test_contract_linearised_functions() { let unit = fixtures::Counter::build_compilation_unit(); let counter = unit .find_contract_by_name("Counter") .expect("can find Counter contract"); - let functions = counter.compute_linearised_functions(); + let functions = counter.linearised_functions(); assert_eq!(functions.len(), 5); assert!(functions[0] @@ -367,13 +367,13 @@ fn test_contract_constructor_and_modifiers() { } #[test] -fn test_contract_compute_linearised_functions_with_overrides() { +fn test_contract_linearised_functions_with_overrides() { let unit = fixtures::Overrides::build_compilation_unit(); let inherited = unit .find_contract_by_name("Inherited") .expect("can find contract"); - let functions = inherited.compute_linearised_functions(); + let functions = inherited.linearised_functions(); assert_eq!(functions.len(), 3); assert!(functions[0] .name()