diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index b54e591d20b2..b6983839ee68 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -36,9 +36,7 @@ #include #include #include -#include -#include #include #include @@ -3060,307 +3058,338 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) } } -bool TypeChecker::visit(MemberAccess const& _memberAccess) +void TypeChecker::performOverloadedResolution( + Type const* _expressionObjectType, + MemberList::MemberMap& _possibleMembers, + FuncCallArguments const& _arguments +) const { - _memberAccess.expression().accept(*this); - Type const* exprType = type(_memberAccess.expression()); - ASTString const& memberName = _memberAccess.memberName(); - - auto& annotation = _memberAccess.annotation(); - - // Retrieve the types of the arguments if this is used to call a function. - auto const& arguments = annotation.arguments; - MemberList::MemberMap possibleMembers = exprType->members(currentDefinitionScope()).membersByName(memberName); - size_t const initialMemberCount = possibleMembers.size(); - if (initialMemberCount > 1 && arguments) + // do overload resolution + for (auto it = _possibleMembers.begin(); it != _possibleMembers.end();) { - // do overload resolution - for (auto it = possibleMembers.begin(); it != possibleMembers.end();) - if ( - it->type->category() == Type::Category::Function && - !dynamic_cast(*it->type).canTakeArguments(*arguments, exprType) - ) - it = possibleMembers.erase(it); - else - ++it; + if ( + it->type->category() == Type::Category::Function && + !dynamic_cast(*it->type).canTakeArguments(_arguments, _expressionObjectType) + ) + it = _possibleMembers.erase(it); + else + ++it; } +} - annotation.isConstant = false; - - if (possibleMembers.empty()) +void TypeChecker::handleUnresolvedMemberAccessErrors( + MemberAccess const& _memberAccess, + Type const* _expressionObjectType, + ASTString const& _memberName, + size_t const _possibleMemberCountBeforeOverloading) const +{ + if (_possibleMemberCountBeforeOverloading == 0 && !dynamic_cast(_expressionObjectType)) { - if (initialMemberCount == 0 && !dynamic_cast(exprType)) - { - // Try to see if the member was removed because it is only available for storage types. - auto storageType = TypeProvider::withLocationIfReference( - DataLocation::Storage, - exprType + // Try to see if the member was removed because it is only available for storage types. + auto storageType = TypeProvider::withLocationIfReference( + DataLocation::Storage, + _expressionObjectType + ); + if (!storageType->members(currentDefinitionScope()).membersByName(_memberName).empty()) + m_errorReporter.fatalTypeError( + 4994_error, + _memberAccess.location(), + "Member \"" + _memberName + "\" is not available in " + + _expressionObjectType->humanReadableName() + + " outside of storage." ); - if (!storageType->members(currentDefinitionScope()).membersByName(memberName).empty()) - m_errorReporter.fatalTypeError( - 4994_error, - _memberAccess.location(), - "Member \"" + memberName + "\" is not available in " + - exprType->humanReadableName() + - " outside of storage." - ); - } + } - auto [errorId, description] = [&]() -> std::tuple { - std::string errorMsg = "Member \"" + memberName + "\" not found or not visible " - "after argument-dependent lookup in " + exprType->humanReadableName() + "."; + auto [errorId, description] = [&]() -> std::tuple { + std::string errorMsg = "Member \"" + _memberName + "\" not found or not visible " + "after argument-dependent lookup in " + _expressionObjectType->humanReadableName() + "."; - if (auto const* funType = dynamic_cast(exprType)) - { - TypePointers const& t = funType->returnParameterTypes(); + if (auto const* funType = dynamic_cast(_expressionObjectType)) + { + TypePointers const& t = funType->returnParameterTypes(); - if (memberName == "value") - { - if (funType->kind() == FunctionType::Kind::Creation) - return { - 8827_error, - "Constructor for " + t.front()->humanReadableName() + " must be payable for member \"value\" to be available." - }; - else if ( - funType->kind() == FunctionType::Kind::DelegateCall || - funType->kind() == FunctionType::Kind::BareDelegateCall - ) - return { 8477_error, "Member \"value\" is not allowed in delegated calls due to \"msg.value\" persisting." }; - else - return { 8820_error, "Member \"value\" is only available for payable functions." }; - } + if (_memberName == "value") + { + if (funType->kind() == FunctionType::Kind::Creation) + return { + 8827_error, + "Constructor for " + t.front()->humanReadableName() + " must be payable for member \"value\" to be available." + }; else if ( - t.size() == 1 && ( - t.front()->category() == Type::Category::Struct || - t.front()->category() == Type::Category::Contract - ) + funType->kind() == FunctionType::Kind::DelegateCall || + funType->kind() == FunctionType::Kind::BareDelegateCall ) - return { 6005_error, errorMsg + " Did you intend to call the function?" }; - } - else if (exprType->category() == Type::Category::Contract) - { - for (MemberList::Member const& addressMember: TypeProvider::payableAddress()->nativeMembers(nullptr)) - if (addressMember.name == memberName) - { - auto const* var = dynamic_cast(&_memberAccess.expression()); - std::string varName = var ? var->name() : "..."; - errorMsg += " Use \"address(" + varName + ")." + memberName + "\" to access this address member."; - return { 3125_error, errorMsg }; - } + return { 8477_error, "Member \"value\" is not allowed in delegated calls due to \"msg.value\" persisting." }; + else + return { 8820_error, "Member \"value\" is only available for payable functions." }; } - else if (auto const* addressType = dynamic_cast(exprType)) - { - // Trigger error when using send or transfer with a non-payable fallback function. - if (memberName == "send" || memberName == "transfer") + else if ( + t.size() == 1 && ( + t.front()->category() == Type::Category::Struct || + t.front()->category() == Type::Category::Contract + ) + ) + return { 6005_error, errorMsg + " Did you intend to call the function?" }; + } + else if (_expressionObjectType->category() == Type::Category::Contract) + { + for (MemberList::Member const& addressMember: TypeProvider::payableAddress()->nativeMembers(nullptr)) + if (addressMember.name == _memberName) { - solAssert( - addressType->stateMutability() != StateMutability::Payable, - "Expected address not-payable as members were not found" - ); - - return { 9862_error, "\"send\" and \"transfer\" are only available for objects of type \"address payable\", not \"" + exprType->humanReadableName() + "\"." }; + auto const* var = dynamic_cast(&_memberAccess.expression()); + std::string varName = var ? var->name() : "..."; + errorMsg += " Use \"address(" + varName + ")." + _memberName + "\" to access this address member."; + return { 3125_error, errorMsg }; } + } + else if (auto const* addressType = dynamic_cast(_expressionObjectType)) + { + // Trigger error when using send or transfer with a non-payable fallback function. + if (_memberName == "send" || _memberName == "transfer") + { + solAssert( + addressType->stateMutability() != StateMutability::Payable, + "Expected address not-payable as members were not found" + ); + + return { 9862_error, "\"send\" and \"transfer\" are only available for objects of type \"address payable\", not \"" + _expressionObjectType->humanReadableName() + "\"." }; } + } - return { 9582_error, errorMsg }; - }(); + return { 9582_error, errorMsg }; + }(); - m_errorReporter.fatalTypeError( - errorId, - _memberAccess.location(), - description + m_errorReporter.fatalTypeError( + errorId, + _memberAccess.location(), + description + ); +} + +void TypeChecker::validateAccessMemberFunctionType( + FunctionType const* _accessedMemberFunctionType, + Type const* _expressionObjectType, + ASTString const& _memberName, + SourceLocation const& _location, + bool _hasArguments, + bool _isDefined +) const +{ + solAssert( + !_accessedMemberFunctionType->hasBoundFirstArgument() || + _expressionObjectType->isImplicitlyConvertibleTo(*_accessedMemberFunctionType->selfType()), + "Function \"" + _memberName + "\" cannot be called on an object of type " + + _expressionObjectType->humanReadableName() + + " (expected " + _accessedMemberFunctionType->selfType()->humanReadableName() + ")." + ); + + if ( + dynamic_cast(_expressionObjectType) && + !_isDefined && + (_memberName == "value" || _memberName == "gas") + ) + m_errorReporter.typeError( + 1621_error, + _location, + "Using \"." + _memberName + "(...)\" is deprecated. Use \"{" + _memberName + ": ...}\" instead." + ); + + if ( + _accessedMemberFunctionType->kind() == FunctionType::Kind::ArrayPush && + _hasArguments && + _expressionObjectType->containsNestedMapping() + ) + m_errorReporter.typeError( + 8871_error, + _location, + "Storage arrays with nested mappings do not support .push()." + ); + + if ( + _accessedMemberFunctionType->kind() == FunctionType::Kind::Send || + _accessedMemberFunctionType->kind() == FunctionType::Kind::Transfer + ) + m_errorReporter.warning( + 9207_error, + _location, + fmt::format( + "'{}' is deprecated and scheduled for removal. Use 'call{{value: }}(\"\")' instead.", + _accessedMemberFunctionType->kind() == FunctionType::Kind::Send ? "send" : "transfer" + ) + ); +} + +bool TypeChecker::visit(MemberAccess const& _memberAccess) +{ + _memberAccess.expression().accept(*this); + Type const* expressionObjectType = type(_memberAccess.expression()); + ASTString const& memberName = _memberAccess.memberName(); + + auto& accessedMemberAnnotation = _memberAccess.annotation(); + + // Retrieve the types of the arguments if this is used to call a function. + auto const& arguments = accessedMemberAnnotation.arguments; + MemberList::MemberMap possibleMembers = expressionObjectType->members(currentDefinitionScope()).membersByName(memberName); + size_t const possibleMemberCountBeforeOverloading = possibleMembers.size(); + if (possibleMemberCountBeforeOverloading > 1 && arguments) + performOverloadedResolution(expressionObjectType, possibleMembers, *arguments); + + accessedMemberAnnotation.isConstant = false; + + if (possibleMembers.empty()) + handleUnresolvedMemberAccessErrors( + _memberAccess, + expressionObjectType, + memberName, + possibleMemberCountBeforeOverloading ); - } else if (possibleMembers.size() > 1) m_errorReporter.fatalTypeError( 6675_error, _memberAccess.location(), "Member \"" + memberName + "\" not unique " - "after argument-dependent lookup in " + exprType->humanReadableName() + + "after argument-dependent lookup in " + expressionObjectType->humanReadableName() + (memberName == "value" ? " - did you forget the \"payable\" modifier?" : ".") ); - annotation.referencedDeclaration = possibleMembers.front().declaration; - annotation.type = possibleMembers.front().type; + accessedMemberAnnotation.referencedDeclaration = possibleMembers.front().declaration; + accessedMemberAnnotation.type = possibleMembers.front().type; + // Lookup type required to find the function declaration. Set default to `static`. VirtualLookup requiredLookup = VirtualLookup::Static; - if (auto funType = dynamic_cast(annotation.type)) + if (auto accessedMemberFunctionTypeType = dynamic_cast(accessedMemberAnnotation.type)) { - solAssert( - !funType->hasBoundFirstArgument() || exprType->isImplicitlyConvertibleTo(*funType->selfType()), - "Function \"" + memberName + "\" cannot be called on an object of type " + - exprType->humanReadableName() + " (expected " + funType->selfType()->humanReadableName() + ")." - ); - - if ( - dynamic_cast(exprType) && - !annotation.referencedDeclaration && - (memberName == "value" || memberName == "gas") - ) - m_errorReporter.typeError( - 1621_error, - _memberAccess.location(), - "Using \"." + memberName + "(...)\" is deprecated. Use \"{" + memberName + ": ...}\" instead." - ); - - if ( - funType->kind() == FunctionType::Kind::ArrayPush && - arguments.value().numArguments() != 0 && - exprType->containsNestedMapping() - ) - m_errorReporter.typeError( - 8871_error, - _memberAccess.location(), - "Storage arrays with nested mappings do not support .push()." - ); - - if (!funType->hasBoundFirstArgument()) - if (auto typeType = dynamic_cast(exprType)) + // Update required lookup + if (!accessedMemberFunctionTypeType->hasBoundFirstArgument()) + if (auto typeType = dynamic_cast(expressionObjectType)) { auto contractType = dynamic_cast(typeType->actualType()); if (contractType && contractType->isSuper()) requiredLookup = VirtualLookup::Super; } - if ( - funType->kind() == FunctionType::Kind::Send || - funType->kind() == FunctionType::Kind::Transfer - ) - m_errorReporter.warning( - 9207_error, - _memberAccess.location(), - fmt::format( - "'{}' is deprecated and scheduled for removal. Use 'call{{value: }}(\"\")' instead.", - funType->kind() == FunctionType::Kind::Send ? "send" : "transfer" - ) - ); + // Validate an accessed member function type. + validateAccessMemberFunctionType( + accessedMemberFunctionTypeType, + expressionObjectType, + memberName, + _memberAccess.location(), + arguments && (*arguments).numArguments() > 0, + accessedMemberAnnotation.referencedDeclaration != nullptr + ); } - annotation.requiredLookup = requiredLookup; + accessedMemberAnnotation.requiredLookup = requiredLookup; - if (auto const* structType = dynamic_cast(exprType)) - annotation.isLValue = !structType->dataStoredIn(DataLocation::CallData); - else if (exprType->category() == Type::Category::Array) - annotation.isLValue = false; - else if (exprType->category() == Type::Category::FixedBytes) - annotation.isLValue = false; - else if (TypeType const* typeType = dynamic_cast(exprType)) + // It is going to be assigned to `accessedMemberAnnotation.isLValue` after the switch. + bool isAccessedMemberLValue = false; + // Switch through all possible categories of the expression object type. + switch (expressionObjectType->category()) { - if (ContractType const* contractType = dynamic_cast(typeType->actualType())) + case Type::Category::Struct: + isAccessedMemberLValue = !reinterpret_cast(expressionObjectType)->dataStoredIn(DataLocation::CallData); + break; + case Type::Category::Function: + { + if ( + auto const* expressionObjectFunctionType = reinterpret_cast(expressionObjectType); + expressionObjectFunctionType->hasDeclaration() && + memberName == "selector" + ) { - annotation.isLValue = annotation.referencedDeclaration->isLValue(); - if ( - auto const* functionType = dynamic_cast(annotation.type); - functionType && - functionType->kind() == FunctionType::Kind::Declaration + if (dynamic_cast(&expressionObjectFunctionType->declaration())) + { + if (auto const* memberAccessParent = dynamic_cast(&_memberAccess.expression())) + { + bool isPure = *memberAccessParent->expression().annotation().isPure; + // Accessing a function selector using `super|this.f.selector`. + if (auto const* exprInt = dynamic_cast(&memberAccessParent->expression())) + if (exprInt->name() == "this" || exprInt->name() == "super") + isPure = true; + + accessedMemberAnnotation.isPure = isPure; + } + } + // In case of event or error definition, the selector is always compile-time constant, as it can be + // a keccak256 hash of the event signature or a function selector in case of an error. + else if ( + dynamic_cast(&expressionObjectFunctionType->declaration()) || + dynamic_cast(&expressionObjectFunctionType->declaration()) ) - annotation.isPure = *_memberAccess.expression().annotation().isPure; + accessedMemberAnnotation.isPure = true; } - else - annotation.isLValue = false; - } - else if (exprType->category() == Type::Category::Module) - { - annotation.isPure = *_memberAccess.expression().annotation().isPure; - annotation.isLValue = false; + break; } - else - annotation.isLValue = false; - - // TODO some members might be pure, but for example `address(0x123).balance` is not pure - // although every subexpression is, so leaving this limited for now. - if (auto tt = dynamic_cast(exprType)) + case Type::Category::TypeType: { - if ( - tt->actualType()->category() == Type::Category::Enum || - tt->actualType()->category() == Type::Category::UserDefinedValueType - ) - annotation.isPure = true; - - // `concat` purity depends also on its arguments, but this is checked later, in visit(FunctionCall...) - // This covers `bytes.concat` and `string.concat`. - if (tt->actualType()->category() == Type::Category::Array) + // TODO some members might be pure, but for example `address(0x123).balance` is not pure + // although every subexpression is, so leaving this limited for now. + auto const* expressionObjectTypeType = reinterpret_cast(expressionObjectType); + switch (expressionObjectTypeType->actualType()->category()) { + case Type::Category::Array: + { + // `concat` purity depends also on its arguments, but this is checked later, in visit(FunctionCall...) + // This covers `bytes.concat` and `string.concat`. if ( - auto const* funcType = dynamic_cast(annotation.type); + auto const* funcType = dynamic_cast(accessedMemberAnnotation.type); funcType && ( funcType->kind() == FunctionType::Kind::StringConcat || funcType->kind() == FunctionType::Kind::BytesConcat ) ) - annotation.isPure = true; - } - } - if ( - auto const* functionType = dynamic_cast(exprType); - functionType && - functionType->hasDeclaration() && - memberName == "selector" - ) - { - if (dynamic_cast(&functionType->declaration())) - { - if (auto const* parentAccess = dynamic_cast(&_memberAccess.expression())) - { - bool isPure = *parentAccess->expression().annotation().isPure; - // Accessing a function selector using `super|this.f.selector`. - if (auto const* exprInt = dynamic_cast(&parentAccess->expression())) - if (exprInt->name() == "this" || exprInt->name() == "super") - isPure = true; + accessedMemberAnnotation.isPure = true; + else + solAssert(false, "Impossible function type for array TypeType "); - annotation.isPure = isPure; - } + break; } - // In case of event or error definition the selector is always compile-time constant, as it can be - // a keccak256 hash of the event signature or a function selector in case of an error. - else if ( - dynamic_cast(&functionType->declaration()) || - dynamic_cast(&functionType->declaration()) - ) - annotation.isPure = true; - } - - if ( - auto const* varDecl = dynamic_cast(annotation.referencedDeclaration); - !annotation.isPure.set() && - varDecl && - varDecl->isConstant() - ) - annotation.isPure = true; - - if (auto magicType = dynamic_cast(exprType)) - { - if (magicType->kind() == MagicType::Kind::ABI) - annotation.isPure = true; - else if (magicType->kind() == MagicType::Kind::MetaType && ( - memberName == "creationCode" || memberName == "runtimeCode" - )) + case Type::Category::Contract: { - annotation.isPure = true; - ContractType const& accessedContractType = dynamic_cast(*magicType->typeArgument()); - solAssert(!accessedContractType.isSuper(), ""); + isAccessedMemberLValue = accessedMemberAnnotation.referencedDeclaration->isLValue(); if ( - memberName == "runtimeCode" && - !accessedContractType.immutableVariables().empty() + auto const* accessedMemberFunctionType = dynamic_cast(accessedMemberAnnotation.type); + accessedMemberFunctionType && + accessedMemberFunctionType->kind() == FunctionType::Kind::Declaration ) - m_errorReporter.typeError( - 9274_error, - _memberAccess.location(), - "\"runtimeCode\" is not available for contracts containing immutable variables." - ); + accessedMemberAnnotation.isPure = *_memberAccess.expression().annotation().isPure; + break; } - else if (magicType->kind() == MagicType::Kind::MetaType && memberName == "name") - annotation.isPure = true; - else if (magicType->kind() == MagicType::Kind::MetaType && memberName == "interfaceId") - annotation.isPure = true; - else if ( - magicType->kind() == MagicType::Kind::MetaType && - (memberName == "min" || memberName == "max") - ) - annotation.isPure = true; - else if (magicType->kind() == MagicType::Kind::Block) + case Type::Category::Enum: + case Type::Category::UserDefinedValueType: + accessedMemberAnnotation.isPure = true; + break; + // Empty cases. + case Type::Category::Address: + case Type::Category::Integer: + case Type::Category::RationalNumber: + case Type::Category::StringLiteral: + case Type::Category::Bool: + case Type::Category::FixedPoint: + case Type::Category::ArraySlice: + case Type::Category::FixedBytes: + case Type::Category::Struct: + case Type::Category::Function: + case Type::Category::Tuple: + case Type::Category::Mapping: + case Type::Category::TypeType: + case Type::Category::Modifier: + case Type::Category::Magic: + case Type::Category::Module: + case Type::Category::InaccessibleDynamic: + break; + } + break; + } + case Type::Category::Magic: + { + auto const* expressionObjectMagicType = reinterpret_cast(expressionObjectType); + + switch (expressionObjectMagicType->kind()) + { + case MagicType::Kind::Block: { if (memberName == "chainid" && !m_evmVersion.hasChainID()) m_errorReporter.typeError( @@ -3392,22 +3421,88 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) _memberAccess.location(), "Since the VM version paris, \"difficulty\" was replaced by \"prevrandao\", which now returns a random number based on the beacon chain." ); + break; } + case MagicType::Kind::ABI: + accessedMemberAnnotation.isPure = true; + break; + case MagicType::Kind::MetaType: + { + if (memberName == "creationCode" || memberName == "runtimeCode") + { + accessedMemberAnnotation.isPure = true; + ContractType const& accessedContractType = dynamic_cast(*expressionObjectMagicType->typeArgument()); + solAssert(!accessedContractType.isSuper(), ""); + if ( + memberName == "runtimeCode" && + !accessedContractType.immutableVariables().empty() + ) + m_errorReporter.typeError( + 9274_error, + _memberAccess.location(), + "\"runtimeCode\" is not available for contracts containing immutable variables." + ); + } else if ( + memberName == "name" || + memberName == "interfaceId" || + memberName == "min" || + memberName == "max" + ) + accessedMemberAnnotation.isPure = true; + break; + } + // Empty cases. + case MagicType::Kind::Message: + case MagicType::Kind::Transaction: + case MagicType::Kind::Error: + break; + } + break; + } + case Type::Category::Module: + accessedMemberAnnotation.isPure = *_memberAccess.expression().annotation().isPure; + break; + case Type::Category::Address: + if (memberName == "codehash" && !m_evmVersion.hasExtCodeHash()) + m_errorReporter.typeError( + 7598_error, + _memberAccess.location(), + "\"codehash\" is not supported by the VM version." + ); + break; + // Empty cases. + case Type::Category::Integer: + case Type::Category::RationalNumber: + case Type::Category::StringLiteral: + case Type::Category::Bool: + case Type::Category::FixedPoint: + case Type::Category::FixedBytes: + case Type::Category::Array: + case Type::Category::ArraySlice: + case Type::Category::Contract: + case Type::Category::Enum: + case Type::Category::UserDefinedValueType: + case Type::Category::Tuple: + case Type::Category::Mapping: + case Type::Category::Modifier: + case Type::Category::InaccessibleDynamic: + break; } + accessedMemberAnnotation.isLValue = isAccessedMemberLValue; + + // TODO: Leave it for now, but it should be moved to TypeType -> Contract case. + // We do not want to change the logic in refactor PR. if ( - _memberAccess.expression().annotation().type->category() == Type::Category::Address && - memberName == "codehash" && - !m_evmVersion.hasExtCodeHash() + auto const* varDecl = dynamic_cast(accessedMemberAnnotation.referencedDeclaration); + !accessedMemberAnnotation.isPure.set() && + varDecl && + varDecl->isConstant() ) - m_errorReporter.typeError( - 7598_error, - _memberAccess.location(), - "\"codehash\" is not supported by the VM version." - ); + accessedMemberAnnotation.isPure = true; - if (!annotation.isPure.set()) - annotation.isPure = false; + if (!accessedMemberAnnotation.isPure.set()) + accessedMemberAnnotation.isPure = false; return false; } diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index e4bc12705314..8267642f1db0 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -189,6 +189,53 @@ class TypeChecker: private ASTConstVisitor return m_currentSourceUnit; } + /// MemberAccess visitor helpers: + /// + /// Resolves overloaded functions by filtering out inapplicable candidates based on the provided arguments + /// and the object type of the expression. This process ensures that only valid functions from the given set + /// of possible members remain, based on argument compatibility and type constraints. + /// @param _expressionObjectType The type of the object on which the member function is invoked. + /// @param _possibleMembers A map of possible member functions of the object. This map will be modified in-place, + /// removing members that do not match the provided arguments or object type. + /// @param _arguments The list of arguments provided in the function call, used to validate compatibility with + /// candidate members. + void performOverloadedResolution( + Type const* _expressionObjectType, + MemberList::MemberMap& _possibleMembers, + FuncCallArguments const& _arguments + ) const; + + /// Handles errors related to accessing unresolved members. + /// Collects and processes errors for member access operations where the member could not be resolved. + /// @param _memberAccess The member access expression where the unresolved member access occurred. + /// @param _expressionObjectType The type of the object being accessed. + /// @param _memberName The name of the member that could not be resolved. + /// @param _possibleMemberCountBeforeOverloading The initial count of possible members before overloading resolution. + void handleUnresolvedMemberAccessErrors( + MemberAccess const& _memberAccess, + Type const* _expressionObjectType, + ASTString const& _memberName, + size_t _possibleMemberCountBeforeOverloading + ) const; + + /// Validates access to a member function of a given type, ensuring that the invocation + /// is consistent with the expected types and semantics. Reports errors and warnings + /// for invalid access, use of deprecated features, and unsupported operations. + /// @param _accessedMemberFunctionType The type of the member function that is being accessed. + /// @param _expressionObjectType The type of the object on which the member function is accessed. + /// @param _memberName The name of the member function being accessed. + /// @param _location The source location where the member function is accessed. + /// @param _hasArguments Indicates whether the member function is accessed without arguments. + /// @param _isDefined Specifies if the member function is fully defined or abstract. + void validateAccessMemberFunctionType( + FunctionType const* _accessedMemberFunctionType, + Type const* _expressionObjectType, + ASTString const& _memberName, + langutil::SourceLocation const& _location, + bool _hasArguments, + bool _isDefined + ) const; + SourceUnit const* m_currentSourceUnit = nullptr; ContractDefinition const* m_currentContract = nullptr;