From e93222af249abf7366dfcd4375904ff4497003a5 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 | 40 +++++++++++++++++++ ...unction_vs_native_property_with_parens.sol | 20 ++++++++++ ...tion_vs_native_property_without_parens.sol | 20 ++++++++++ .../native_isContract_property.sol | 10 +++++ 4 files changed, 90 insertions(+) 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..79ccad507128 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -3186,6 +3186,46 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) ++it; } + // When both a native property and a library function share the same name, + // disambiguate based on call syntax: + // - With parentheses (function call): prefer function members (library) + // - Without parentheses (property access): prefer non-function members (native) + // This mirrors the identifier resolution pattern at lines 3693-3745 where + // VariableDeclarations are preferred without parentheses. + if (possibleMembers.size() > 1) + { + bool hasFunction = false; + bool hasNonFunction = false; + for (auto const& member: possibleMembers) + { + if (member.type->category() == Type::Category::Function) + hasFunction = true; + else + hasNonFunction = true; + } + + if (hasFunction && hasNonFunction) + { + MemberList::MemberMap filtered; + if (arguments) + { + // Called with parentheses - keep only functions (library) + for (auto const& member: possibleMembers) + if (member.type->category() == Type::Category::Function) + filtered.push_back(member); + } + else + { + // Accessed without parentheses - keep only non-functions (native property) + for (auto const& member: possibleMembers) + if (member.type->category() != Type::Category::Function) + filtered.push_back(member); + } + if (filtered.size() == 1) + possibleMembers = std::move(filtered); + } + } + annotation.isConstant = false; if (possibleMembers.empty()) 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..dd46794aacdb --- /dev/null +++ b/test/libsolidity/syntaxTests/memberLookup/library_function_vs_native_property_without_parens.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0; + +// Test that native property is preferred over library function when accessed without parentheses. +// This allows Tron's native address.isContract property to work alongside library functions. +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 - should use native property + return a.isContract; + } +} +// ---- 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; + } +} +// ----