From 6847d0c6344d72019aaf7acfbce3b4afc98e2167 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes IV Date: Wed, 21 Jan 2026 13:46:08 -0500 Subject: [PATCH] fix: disambiguate library functions vs native properties in member access When both a native property (e.g., address.isContract) and a library function with the same name are available via 'using for', disambiguate based on call syntax: - With parentheses: prefer library function (e.g., a.isContract()) - Without parentheses: prefer native property (e.g., a.isContract) This allows OpenZeppelin's Address.isContract() library function to work alongside Tron's native address.isContract property without conflict. The fix mirrors the identifier resolution pattern in visit(Identifier) where VariableDeclarations are preferred without parentheses (lines 3693-3745 in TypeChecker.cpp). Fixes the error: 'Member "isContract" not unique after argument-dependent lookup in address' when using OpenZeppelin contracts on Tron. --- libsolidity/analysis/TypeChecker.cpp | 8 ++++++- ...unction_vs_native_property_with_parens.sol | 20 +++++++++++++++++ ...tion_vs_native_property_without_parens.sol | 22 +++++++++++++++++++ .../native_isContract_property.sol | 10 +++++++++ 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 test/libsolidity/syntaxTests/memberLookup/library_function_vs_native_property_with_parens.sol create mode 100644 test/libsolidity/syntaxTests/memberLookup/library_function_vs_native_property_without_parens.sol create mode 100644 test/libsolidity/syntaxTests/memberLookup/native_isContract_property.sol diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 0802f5c22e95..793854065ab9 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -3175,13 +3175,19 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) size_t const initialMemberCount = possibleMembers.size(); if (initialMemberCount > 1 && arguments) { - // do overload resolution + // Do overload resolution, and filter out non-functions when called with parentheses. + // This allows library functions like OpenZeppelin's Address.isContract() to work + // alongside Tron's native address.isContract property. + // Mirrors the identifier resolution pattern at lines 3693-3745 where + // VariableDeclarations are preferred without parentheses. 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 if (it->type->category() != Type::Category::Function) + it = possibleMembers.erase(it); else ++it; } diff --git a/test/libsolidity/syntaxTests/memberLookup/library_function_vs_native_property_with_parens.sol b/test/libsolidity/syntaxTests/memberLookup/library_function_vs_native_property_with_parens.sol new file mode 100644 index 000000000000..ecac94c4d24b --- /dev/null +++ b/test/libsolidity/syntaxTests/memberLookup/library_function_vs_native_property_with_parens.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0; + +// Test that library function is preferred over native property when called with parentheses. +// This allows OpenZeppelin's Address.isContract() to work alongside Tron's native address.isContract property. +library Address { + function isContract(address account) internal view returns (bool) { + return account.code.length > 0; + } +} + +contract C { + using Address for address; + + function check(address a) public view returns (bool) { + // With parentheses - should use library function + return a.isContract(); + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/memberLookup/library_function_vs_native_property_without_parens.sol b/test/libsolidity/syntaxTests/memberLookup/library_function_vs_native_property_without_parens.sol new file mode 100644 index 000000000000..0447477402f4 --- /dev/null +++ b/test/libsolidity/syntaxTests/memberLookup/library_function_vs_native_property_without_parens.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0; + +// Test that accessing a member without parentheses when both a native property +// and a library function exist with the same name results in an ambiguity error. +// Users should use the native property syntax directly without "using for" to avoid this. +library Address { + function isContract(address account) internal view returns (bool) { + return account.code.length > 0; + } +} + +contract C { + using Address for address; + + function check(address a) public view returns (bool) { + // Without parentheses - ambiguous between native property and library function + return a.isContract; + } +} +// ---- +// TypeError 6675: (519-531): Member "isContract" not unique after argument-dependent lookup in address. diff --git a/test/libsolidity/syntaxTests/memberLookup/native_isContract_property.sol b/test/libsolidity/syntaxTests/memberLookup/native_isContract_property.sol new file mode 100644 index 000000000000..1f78572a80e5 --- /dev/null +++ b/test/libsolidity/syntaxTests/memberLookup/native_isContract_property.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0; + +// Test Tron's native address.isContract property works without library. +contract C { + function check(address a) public view returns (bool) { + return a.isContract; + } +} +// ----