From 917348bb77cc2995f990c2bc4fbed225ed28c39c Mon Sep 17 00:00:00 2001 From: rodiazet Date: Fri, 6 Feb 2026 12:14:59 +0100 Subject: [PATCH 1/9] Extract self-contained code fragments to a functions. --- libsolidity/analysis/TypeChecker.cpp | 342 ++++++++++++++++----------- libsolidity/analysis/TypeChecker.h | 13 + 2 files changed, 218 insertions(+), 137 deletions(-) diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index b54e591d20b2..64bd3038144a 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -3060,142 +3060,164 @@ 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 + ); +} + +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; + // TODO: Explain VirtualLookup requiredLookup = VirtualLookup::Static; - if (auto funType = dynamic_cast(annotation.type)) + if (auto funType = dynamic_cast(accessedMemberAnnotation.type)) { solAssert( - !funType->hasBoundFirstArgument() || exprType->isImplicitlyConvertibleTo(*funType->selfType()), + !funType->hasBoundFirstArgument() || expressionObjectType->isImplicitlyConvertibleTo(*funType->selfType()), "Function \"" + memberName + "\" cannot be called on an object of type " + - exprType->humanReadableName() + " (expected " + funType->selfType()->humanReadableName() + ")." + expressionObjectType->humanReadableName() + " (expected " + funType->selfType()->humanReadableName() + ")." ); if ( - dynamic_cast(exprType) && - !annotation.referencedDeclaration && + dynamic_cast(expressionObjectType) && + !accessedMemberAnnotation.referencedDeclaration && (memberName == "value" || memberName == "gas") ) m_errorReporter.typeError( @@ -3207,7 +3229,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) if ( funType->kind() == FunctionType::Kind::ArrayPush && arguments.value().numArguments() != 0 && - exprType->containsNestedMapping() + expressionObjectType->containsNestedMapping() ) m_errorReporter.typeError( 8871_error, @@ -3216,7 +3238,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) ); if (!funType->hasBoundFirstArgument()) - if (auto typeType = dynamic_cast(exprType)) + if (auto typeType = dynamic_cast(expressionObjectType)) { auto contractType = dynamic_cast(typeType->actualType()); if (contractType && contractType->isSuper()) @@ -3237,64 +3259,110 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) ); } - annotation.requiredLookup = requiredLookup; + accessedMemberAnnotation.requiredLookup = requiredLookup; + + switch (expressionObjectType->category()) + { + case Type::Category::Address: + break; + case Type::Category::Integer: + break; + case Type::Category::RationalNumber: + break; + case Type::Category::StringLiteral: + break; + case Type::Category::Bool: + break; + case Type::Category::FixedPoint: + break; + case Type::Category::Array: + break; + case Type::Category::ArraySlice: + break; + case Type::Category::FixedBytes: + break; + case Type::Category::Contract: + break; + case Type::Category::Struct: + break; + case Type::Category::Function: + break; + case Type::Category::Enum: + break; + case Type::Category::UserDefinedValueType: + break; + case Type::Category::Tuple: + break; + case Type::Category::Mapping: + break; + case Type::Category::TypeType: + break; + case Type::Category::Modifier: + break; + case Type::Category::Magic: + break; + case Type::Category::Module: + break; + case Type::Category::InaccessibleDynamic: + break; + } - 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)) + if (auto const* structType = dynamic_cast(expressionObjectType)) + accessedMemberAnnotation.isLValue = !structType->dataStoredIn(DataLocation::CallData); + else if (expressionObjectType->category() == Type::Category::Array) + accessedMemberAnnotation.isLValue = false; + else if (expressionObjectType->category() == Type::Category::FixedBytes) + accessedMemberAnnotation.isLValue = false; + else if (TypeType const* typeType = dynamic_cast(expressionObjectType)) { if (ContractType const* contractType = dynamic_cast(typeType->actualType())) { - annotation.isLValue = annotation.referencedDeclaration->isLValue(); + accessedMemberAnnotation.isLValue = accessedMemberAnnotation.referencedDeclaration->isLValue(); if ( - auto const* functionType = dynamic_cast(annotation.type); + auto const* functionType = dynamic_cast(accessedMemberAnnotation.type); functionType && functionType->kind() == FunctionType::Kind::Declaration ) - annotation.isPure = *_memberAccess.expression().annotation().isPure; + accessedMemberAnnotation.isPure = *_memberAccess.expression().annotation().isPure; } else - annotation.isLValue = false; + accessedMemberAnnotation.isLValue = false; } - else if (exprType->category() == Type::Category::Module) + else if (expressionObjectType->category() == Type::Category::Module) { - annotation.isPure = *_memberAccess.expression().annotation().isPure; - annotation.isLValue = false; + accessedMemberAnnotation.isPure = *_memberAccess.expression().annotation().isPure; + accessedMemberAnnotation.isLValue = false; } else - annotation.isLValue = false; + accessedMemberAnnotation.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)) + if (auto tt = dynamic_cast(expressionObjectType)) { if ( tt->actualType()->category() == Type::Category::Enum || tt->actualType()->category() == Type::Category::UserDefinedValueType ) - annotation.isPure = true; + accessedMemberAnnotation.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) { 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; + accessedMemberAnnotation.isPure = true; } } if ( - auto const* functionType = dynamic_cast(exprType); + auto const* functionType = dynamic_cast(expressionObjectType); functionType && functionType->hasDeclaration() && memberName == "selector" @@ -3310,7 +3378,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) if (exprInt->name() == "this" || exprInt->name() == "super") isPure = true; - annotation.isPure = isPure; + accessedMemberAnnotation.isPure = isPure; } } // In case of event or error definition the selector is always compile-time constant, as it can be @@ -3319,26 +3387,26 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) dynamic_cast(&functionType->declaration()) || dynamic_cast(&functionType->declaration()) ) - annotation.isPure = true; + accessedMemberAnnotation.isPure = true; } if ( - auto const* varDecl = dynamic_cast(annotation.referencedDeclaration); - !annotation.isPure.set() && + auto const* varDecl = dynamic_cast(accessedMemberAnnotation.referencedDeclaration); + !accessedMemberAnnotation.isPure.set() && varDecl && varDecl->isConstant() ) - annotation.isPure = true; + accessedMemberAnnotation.isPure = true; - if (auto magicType = dynamic_cast(exprType)) + if (auto magicType = dynamic_cast(expressionObjectType)) { if (magicType->kind() == MagicType::Kind::ABI) - annotation.isPure = true; + accessedMemberAnnotation.isPure = true; else if (magicType->kind() == MagicType::Kind::MetaType && ( memberName == "creationCode" || memberName == "runtimeCode" )) { - annotation.isPure = true; + accessedMemberAnnotation.isPure = true; ContractType const& accessedContractType = dynamic_cast(*magicType->typeArgument()); solAssert(!accessedContractType.isSuper(), ""); if ( @@ -3352,14 +3420,14 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) ); } else if (magicType->kind() == MagicType::Kind::MetaType && memberName == "name") - annotation.isPure = true; + accessedMemberAnnotation.isPure = true; else if (magicType->kind() == MagicType::Kind::MetaType && memberName == "interfaceId") - annotation.isPure = true; + accessedMemberAnnotation.isPure = true; else if ( magicType->kind() == MagicType::Kind::MetaType && (memberName == "min" || memberName == "max") ) - annotation.isPure = true; + accessedMemberAnnotation.isPure = true; else if (magicType->kind() == MagicType::Kind::Block) { if (memberName == "chainid" && !m_evmVersion.hasChainID()) @@ -3406,8 +3474,8 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) "\"codehash\" is not supported by the VM version." ); - 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..55eb57a8d2c2 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -189,6 +189,19 @@ class TypeChecker: private ASTConstVisitor return m_currentSourceUnit; } + void performOverloadedResolution( + Type const* _expressionObjectType, + MemberList::MemberMap& _possibleMembers, + FuncCallArguments const& _arguments + ) const; + + void handleUnresolvedMemberAccessErrors( + MemberAccess const& _memberAccess, + Type const* _expressionObjectType, + ASTString const& _memberName, + size_t _initialMemberCount + ) const; + SourceUnit const* m_currentSourceUnit = nullptr; ContractDefinition const* m_currentContract = nullptr; From d680e079b83e9f83c2970026308638eb9a16498f Mon Sep 17 00:00:00 2001 From: rodiazet Date: Fri, 6 Feb 2026 12:29:32 +0100 Subject: [PATCH 2/9] Move the first part to switch statement --- libsolidity/analysis/TypeChecker.cpp | 49 +++++++++++----------------- 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 64bd3038144a..b6996ea34706 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -3261,6 +3261,8 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) accessedMemberAnnotation.requiredLookup = requiredLookup; + // It is going to be assigned to `accessedMemberAnnotation.isLValue` after the switch. + bool isAccessedMemberLValue = false; switch (expressionObjectType->category()) { case Type::Category::Address: @@ -3275,15 +3277,15 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) break; case Type::Category::FixedPoint: break; + case Type::Category::FixedBytes: case Type::Category::Array: break; case Type::Category::ArraySlice: break; - case Type::Category::FixedBytes: - break; case Type::Category::Contract: break; case Type::Category::Struct: + isAccessedMemberLValue = !reinterpret_cast(expressionObjectType)->dataStoredIn(DataLocation::CallData); break; case Type::Category::Function: break; @@ -3296,45 +3298,32 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) case Type::Category::Mapping: break; case Type::Category::TypeType: + { + auto const* expressionObjectTypeType = reinterpret_cast(expressionObjectType); + if (dynamic_cast(expressionObjectTypeType->actualType())) + { + isAccessedMemberLValue = accessedMemberAnnotation.referencedDeclaration->isLValue(); + if ( + auto const* accessedMemberFunctionType = dynamic_cast(accessedMemberAnnotation.type); + accessedMemberFunctionType && + accessedMemberFunctionType->kind() == FunctionType::Kind::Declaration + ) + accessedMemberAnnotation.isPure = *_memberAccess.expression().annotation().isPure; + } break; + } case Type::Category::Modifier: break; case Type::Category::Magic: break; case Type::Category::Module: + accessedMemberAnnotation.isPure = *_memberAccess.expression().annotation().isPure; break; case Type::Category::InaccessibleDynamic: break; } - if (auto const* structType = dynamic_cast(expressionObjectType)) - accessedMemberAnnotation.isLValue = !structType->dataStoredIn(DataLocation::CallData); - else if (expressionObjectType->category() == Type::Category::Array) - accessedMemberAnnotation.isLValue = false; - else if (expressionObjectType->category() == Type::Category::FixedBytes) - accessedMemberAnnotation.isLValue = false; - else if (TypeType const* typeType = dynamic_cast(expressionObjectType)) - { - if (ContractType const* contractType = dynamic_cast(typeType->actualType())) - { - accessedMemberAnnotation.isLValue = accessedMemberAnnotation.referencedDeclaration->isLValue(); - if ( - auto const* functionType = dynamic_cast(accessedMemberAnnotation.type); - functionType && - functionType->kind() == FunctionType::Kind::Declaration - ) - accessedMemberAnnotation.isPure = *_memberAccess.expression().annotation().isPure; - } - else - accessedMemberAnnotation.isLValue = false; - } - else if (expressionObjectType->category() == Type::Category::Module) - { - accessedMemberAnnotation.isPure = *_memberAccess.expression().annotation().isPure; - accessedMemberAnnotation.isLValue = false; - } - else - accessedMemberAnnotation.isLValue = false; + accessedMemberAnnotation.isLValue = isAccessedMemberLValue; // 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. From 8ecde5bc3f4a5afca90a56d7c676ea549551a572 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Fri, 6 Feb 2026 12:54:33 +0100 Subject: [PATCH 3/9] Move the second part to switch statement Remove unnecessary includes --- libsolidity/analysis/TypeChecker.cpp | 248 ++++++++++++++++----------- 1 file changed, 146 insertions(+), 102 deletions(-) diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index b6996ea34706..d763466c50c5 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -36,9 +36,7 @@ #include #include #include -#include -#include #include #include @@ -3263,6 +3261,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) // 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()) { case Type::Category::Address: @@ -3288,7 +3287,36 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) isAccessedMemberLValue = !reinterpret_cast(expressionObjectType)->dataStoredIn(DataLocation::CallData); break; case Type::Category::Function: + { + if ( + auto const* expressionObjectFunctionType = reinterpret_cast(expressionObjectType); + expressionObjectFunctionType->hasDeclaration() && + memberName == "selector" + ) + { + 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()) + ) + accessedMemberAnnotation.isPure = true; + } break; + } case Type::Category::Enum: break; case Type::Category::UserDefinedValueType: @@ -3299,46 +3327,27 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) break; case Type::Category::TypeType: { + // 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); - if (dynamic_cast(expressionObjectTypeType->actualType())) + switch (expressionObjectTypeType->actualType()->category()) { - isAccessedMemberLValue = accessedMemberAnnotation.referencedDeclaration->isLValue(); - if ( - auto const* accessedMemberFunctionType = dynamic_cast(accessedMemberAnnotation.type); - accessedMemberFunctionType && - accessedMemberFunctionType->kind() == FunctionType::Kind::Declaration - ) - accessedMemberAnnotation.isPure = *_memberAccess.expression().annotation().isPure; - } - break; - } - case Type::Category::Modifier: - break; - case Type::Category::Magic: - break; - case Type::Category::Module: - accessedMemberAnnotation.isPure = *_memberAccess.expression().annotation().isPure; - break; - case Type::Category::InaccessibleDynamic: - break; - } - - accessedMemberAnnotation.isLValue = isAccessedMemberLValue; - - // 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(expressionObjectType)) - { - if ( - tt->actualType()->category() == Type::Category::Enum || - tt->actualType()->category() == Type::Category::UserDefinedValueType - ) - accessedMemberAnnotation.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) + case Type::Category::Address: + break; + case Type::Category::Integer: + break; + case Type::Category::RationalNumber: + break; + case Type::Category::StringLiteral: + break; + case Type::Category::Bool: + break; + case Type::Category::FixedPoint: + break; + 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(accessedMemberAnnotation.type); funcType && @@ -3348,76 +3357,60 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) ) ) accessedMemberAnnotation.isPure = true; - } - } - if ( - auto const* functionType = dynamic_cast(expressionObjectType); - 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; + else + solAssert(false, "Impossible function type for array TypeType "); - accessedMemberAnnotation.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()) - ) - accessedMemberAnnotation.isPure = true; - } - - if ( - auto const* varDecl = dynamic_cast(accessedMemberAnnotation.referencedDeclaration); - !accessedMemberAnnotation.isPure.set() && - varDecl && - varDecl->isConstant() - ) - accessedMemberAnnotation.isPure = true; - - if (auto magicType = dynamic_cast(expressionObjectType)) - { - if (magicType->kind() == MagicType::Kind::ABI) - accessedMemberAnnotation.isPure = true; - else if (magicType->kind() == MagicType::Kind::MetaType && ( - memberName == "creationCode" || memberName == "runtimeCode" - )) + case Type::Category::ArraySlice: + break; + case Type::Category::FixedBytes: + break; + case Type::Category::Contract: { - accessedMemberAnnotation.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") - accessedMemberAnnotation.isPure = true; - else if (magicType->kind() == MagicType::Kind::MetaType && memberName == "interfaceId") - accessedMemberAnnotation.isPure = true; - else if ( - magicType->kind() == MagicType::Kind::MetaType && - (memberName == "min" || memberName == "max") - ) + case Type::Category::Struct: + break; + case Type::Category::Function: + break; + case Type::Category::Enum: + case Type::Category::UserDefinedValueType: accessedMemberAnnotation.isPure = true; - else if (magicType->kind() == MagicType::Kind::Block) + break; + case Type::Category::Tuple: + break; + case Type::Category::Mapping: + break; + case Type::Category::TypeType: + break; + case Type::Category::Modifier: + break; + case Type::Category::Magic: + break; + case Type::Category::Module: + break; + case Type::Category::InaccessibleDynamic: + break; + } + break; + } + case Type::Category::Modifier: + 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( @@ -3449,8 +3442,59 @@ 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::Message: + break; + case MagicType::Kind::Transaction: + break; + case MagicType::Kind::ABI: + accessedMemberAnnotation.isPure = true; + break; + case MagicType::Kind::Error: + 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; } + break; } + case Type::Category::Module: + accessedMemberAnnotation.isPure = *_memberAccess.expression().annotation().isPure; + break; + case Type::Category::InaccessibleDynamic: + break; + } + + accessedMemberAnnotation.isLValue = isAccessedMemberLValue; + + if ( + auto const* varDecl = dynamic_cast(accessedMemberAnnotation.referencedDeclaration); + !accessedMemberAnnotation.isPure.set() && + varDecl && + varDecl->isConstant() + ) + accessedMemberAnnotation.isPure = true; if ( _memberAccess.expression().annotation().type->category() == Type::Category::Address && From d598aad6ebc4a2ed3667075cb8750a841ca16026 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Fri, 6 Feb 2026 13:10:33 +0100 Subject: [PATCH 4/9] Switch statements cleanup. --- libsolidity/analysis/TypeChecker.cpp | 97 +++++++++++----------------- 1 file changed, 36 insertions(+), 61 deletions(-) diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index d763466c50c5..2982e84a28b4 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -3264,25 +3264,6 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) // Switch through all possible categories of the expression object type. switch (expressionObjectType->category()) { - case Type::Category::Address: - break; - case Type::Category::Integer: - break; - case Type::Category::RationalNumber: - break; - case Type::Category::StringLiteral: - break; - case Type::Category::Bool: - break; - case Type::Category::FixedPoint: - break; - case Type::Category::FixedBytes: - case Type::Category::Array: - break; - case Type::Category::ArraySlice: - break; - case Type::Category::Contract: - break; case Type::Category::Struct: isAccessedMemberLValue = !reinterpret_cast(expressionObjectType)->dataStoredIn(DataLocation::CallData); break; @@ -3317,14 +3298,6 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) } break; } - case Type::Category::Enum: - break; - case Type::Category::UserDefinedValueType: - break; - case Type::Category::Tuple: - break; - case Type::Category::Mapping: - break; case Type::Category::TypeType: { // TODO some members might be pure, but for example `address(0x123).balance` is not pure @@ -3332,18 +3305,6 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) auto const* expressionObjectTypeType = reinterpret_cast(expressionObjectType); switch (expressionObjectTypeType->actualType()->category()) { - case Type::Category::Address: - break; - case Type::Category::Integer: - break; - case Type::Category::RationalNumber: - break; - case Type::Category::StringLiteral: - break; - case Type::Category::Bool: - break; - case Type::Category::FixedPoint: - break; case Type::Category::Array: { // `concat` purity depends also on its arguments, but this is checked later, in visit(FunctionCall...) @@ -3362,10 +3323,6 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) break; } - case Type::Category::ArraySlice: - break; - case Type::Category::FixedBytes: - break; case Type::Category::Contract: { isAccessedMemberLValue = accessedMemberAnnotation.referencedDeclaration->isLValue(); @@ -3377,33 +3334,32 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) accessedMemberAnnotation.isPure = *_memberAccess.expression().annotation().isPure; break; } - case Type::Category::Struct: - break; - case Type::Category::Function: - break; 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: - break; case Type::Category::Mapping: - break; case Type::Category::TypeType: - break; case Type::Category::Modifier: - break; case Type::Category::Magic: - break; case Type::Category::Module: - break; case Type::Category::InaccessibleDynamic: break; } break; } - case Type::Category::Modifier: - break; case Type::Category::Magic: { auto const* expressionObjectMagicType = reinterpret_cast(expressionObjectType); @@ -3444,16 +3400,11 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) ); break; } - case MagicType::Kind::Message: - break; - case MagicType::Kind::Transaction: - break; case MagicType::Kind::ABI: accessedMemberAnnotation.isPure = true; break; - case MagicType::Kind::Error: - break; case MagicType::Kind::MetaType: + { if (memberName == "creationCode" || memberName == "runtimeCode") { accessedMemberAnnotation.isPure = true; @@ -3477,17 +3428,41 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) 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; + + // 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::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 if ( auto const* varDecl = dynamic_cast(accessedMemberAnnotation.referencedDeclaration); !accessedMemberAnnotation.isPure.set() && From bab014d59635138748bec7bb07fd520fc6d81342 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Fri, 6 Feb 2026 13:12:12 +0100 Subject: [PATCH 5/9] Move `Address` case to the common switch. --- libsolidity/analysis/TypeChecker.cpp | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 2982e84a28b4..536b40e4b803 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -3439,9 +3439,15 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) case Type::Category::Module: accessedMemberAnnotation.isPure = *_memberAccess.expression().annotation().isPure; break; - - // Empty cases. 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: @@ -3471,17 +3477,6 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) ) accessedMemberAnnotation.isPure = true; - if ( - _memberAccess.expression().annotation().type->category() == Type::Category::Address && - memberName == "codehash" && - !m_evmVersion.hasExtCodeHash() - ) - m_errorReporter.typeError( - 7598_error, - _memberAccess.location(), - "\"codehash\" is not supported by the VM version." - ); - if (!accessedMemberAnnotation.isPure.set()) accessedMemberAnnotation.isPure = false; From c3d177b171a77e3837da9d231733929891b4013d Mon Sep 17 00:00:00 2001 From: rodiazet Date: Fri, 6 Feb 2026 15:01:23 +0100 Subject: [PATCH 6/9] Extract function type member validation to a function. --- libsolidity/analysis/TypeChecker.cpp | 107 ++++++++++++++++----------- libsolidity/analysis/TypeChecker.h | 10 +++ 2 files changed, 75 insertions(+), 42 deletions(-) diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 536b40e4b803..4aaebcf989c0 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -3166,6 +3166,59 @@ void TypeChecker::handleUnresolvedMemberAccessErrors( ); } +void TypeChecker::validateAccessMemberFunctionType( + FunctionType const* _accessedMemberFunctionType, + Type const* _expressionObjectType, + ASTString const& _memberName, + SourceLocation const& _location, + bool _emptyArguments, + 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 && + !_emptyArguments && + _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); @@ -3205,37 +3258,10 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) // TODO: Explain VirtualLookup requiredLookup = VirtualLookup::Static; - if (auto funType = dynamic_cast(accessedMemberAnnotation.type)) + if (auto accessedMemberFunctionTypeType = dynamic_cast(accessedMemberAnnotation.type)) { - solAssert( - !funType->hasBoundFirstArgument() || expressionObjectType->isImplicitlyConvertibleTo(*funType->selfType()), - "Function \"" + memberName + "\" cannot be called on an object of type " + - expressionObjectType->humanReadableName() + " (expected " + funType->selfType()->humanReadableName() + ")." - ); - - if ( - dynamic_cast(expressionObjectType) && - !accessedMemberAnnotation.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 && - expressionObjectType->containsNestedMapping() - ) - m_errorReporter.typeError( - 8871_error, - _memberAccess.location(), - "Storage arrays with nested mappings do not support .push()." - ); - - if (!funType->hasBoundFirstArgument()) + // Update required lookup + if (!accessedMemberFunctionTypeType->hasBoundFirstArgument()) if (auto typeType = dynamic_cast(expressionObjectType)) { auto contractType = dynamic_cast(typeType->actualType()); @@ -3243,18 +3269,15 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) 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).numArguments() == 0, + accessedMemberAnnotation.referencedDeclaration != nullptr + ); } accessedMemberAnnotation.requiredLookup = requiredLookup; diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index 55eb57a8d2c2..161ab58cc4fa 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -202,6 +202,16 @@ class TypeChecker: private ASTConstVisitor size_t _initialMemberCount ) const; + void validateAccessMemberFunctionType( + FunctionType const* _accessedMemberFunctionType, + Type const* _expressionObjectType, + ASTString const& _memberName, + langutil::SourceLocation const& _location, + bool _emptyArguments, + bool _isDefined + ) const; + + SourceUnit const* m_currentSourceUnit = nullptr; ContractDefinition const* m_currentContract = nullptr; From eb652b6e28ab67ade06a8f4fc15b26cfe4caf367 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Fri, 6 Feb 2026 13:15:47 +0100 Subject: [PATCH 7/9] Better doc --- libsolidity/analysis/TypeChecker.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 4aaebcf989c0..a5f4f8d07c5b 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -3255,7 +3255,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) accessedMemberAnnotation.referencedDeclaration = possibleMembers.front().declaration; accessedMemberAnnotation.type = possibleMembers.front().type; - // TODO: Explain + // Lookup type required to find the function declaration. Set default to `static`. VirtualLookup requiredLookup = VirtualLookup::Static; if (auto accessedMemberFunctionTypeType = dynamic_cast(accessedMemberAnnotation.type)) @@ -3491,7 +3491,8 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) accessedMemberAnnotation.isLValue = isAccessedMemberLValue; - // TODO: Leave it for now, but it should be moved to TypeType -> Contract case + // 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 ( auto const* varDecl = dynamic_cast(accessedMemberAnnotation.referencedDeclaration); !accessedMemberAnnotation.isPure.set() && From ec377fd95e49e29918847ceeaa8407679ce7c001 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Fri, 6 Feb 2026 15:35:35 +0100 Subject: [PATCH 8/9] Add doc strings and improve naming. --- libsolidity/analysis/TypeChecker.cpp | 4 ++-- libsolidity/analysis/TypeChecker.h | 30 +++++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index a5f4f8d07c5b..1f2882265095 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -3171,7 +3171,7 @@ void TypeChecker::validateAccessMemberFunctionType( Type const* _expressionObjectType, ASTString const& _memberName, SourceLocation const& _location, - bool _emptyArguments, + bool _hasEmptyArguments, bool _isDefined ) const { @@ -3196,7 +3196,7 @@ void TypeChecker::validateAccessMemberFunctionType( if ( _accessedMemberFunctionType->kind() == FunctionType::Kind::ArrayPush && - !_emptyArguments && + !_hasEmptyArguments && _expressionObjectType->containsNestedMapping() ) m_errorReporter.typeError( diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index 161ab58cc4fa..3eef76e75dc1 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -189,29 +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 _initialMemberCount + 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 _hasEmptyArguments 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 _emptyArguments, + bool _hasEmptyArguments, bool _isDefined ) const; - SourceUnit const* m_currentSourceUnit = nullptr; ContractDefinition const* m_currentContract = nullptr; From 06bb313969c1dfa5f8da60b3f72afb1fd2259df4 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Fri, 6 Feb 2026 17:50:03 +0100 Subject: [PATCH 9/9] Add sanity check for arguments. Fix arguments checking. --- libsolidity/analysis/TypeChecker.cpp | 6 +++--- libsolidity/analysis/TypeChecker.h | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 1f2882265095..b6983839ee68 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -3171,7 +3171,7 @@ void TypeChecker::validateAccessMemberFunctionType( Type const* _expressionObjectType, ASTString const& _memberName, SourceLocation const& _location, - bool _hasEmptyArguments, + bool _hasArguments, bool _isDefined ) const { @@ -3196,7 +3196,7 @@ void TypeChecker::validateAccessMemberFunctionType( if ( _accessedMemberFunctionType->kind() == FunctionType::Kind::ArrayPush && - !_hasEmptyArguments && + _hasArguments && _expressionObjectType->containsNestedMapping() ) m_errorReporter.typeError( @@ -3275,7 +3275,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) expressionObjectType, memberName, _memberAccess.location(), - (*arguments).numArguments() == 0, + arguments && (*arguments).numArguments() > 0, accessedMemberAnnotation.referencedDeclaration != nullptr ); } diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index 3eef76e75dc1..8267642f1db0 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -225,14 +225,14 @@ class TypeChecker: private ASTConstVisitor /// @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 _hasEmptyArguments Indicates whether the member function is accessed without arguments. + /// @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 _hasEmptyArguments, + bool _hasArguments, bool _isDefined ) const;