"No unique declaration found" when overloading functions with integers of different sizes

Take the following code:

pragma solidity >=0.8.17;

contract Contract {
    function foo(uint256 arg) internal pure returns (uint256) {
        return arg;
    }

    function foo(uint64 arg) internal pure returns (uint64) {
        return arg;
    }

    function ar() external returns (uint64) {
        uint64 arg = foo(uint64(1));
        return arg;
    }
}

It does not compile:

TypeError: No unique declaration found after argument-dependent lookup.
  --> contracts/Contract.sol:14:22:
   |
14 |         uint64 arg = foo(uint64(1));
   |                      ^^^
Note: Candidate:
 --> contracts/Contract.sol:5:5:
  |
5 |     function foo(uint256 arg) internal pure returns (uint256) {
  |     ^ (Relevant source part starts here and spans across multiple lines).
Note: Candidate:
 --> contracts/Contract.sol:9:5:
  |
9 |     function foo(uint64 arg) internal pure returns (uint64) {
  |     ^ (Relevant source part starts here and spans across multiple lines).

The compiler should have all the information it needs to figure out that it should call foo(uint64) instead of foo(uint256), but it doesn’t do that. However, if I update the first function like this:

function foo(address arg) internal pure returns (address) {
    return arg;
}

Then the code compiles just fine. But this is odd. Why does the compiler distinguish address and uint64 but not uint256 and uint64? foo(uint256) has a different function signature from foo(uint64):

❯ cast sig "foo(uint256)"
0x2fbebd38
❯ cast sig "foo(uint64)"
0xdecb0da1

I’m not sure if this is a bug or just an intentional limitation of function overloading in Solidity. If the former, I would be happy to open a GitHub issue to track this.

Hi @paulrberg ! :wave:
Thank you for the feedback someone from the Solidity team will reply shortly.

address isn’t implicitly convertible to uint256 or vice versa - while uint64 is implicitly convertible to uint256 - that’s the conceptual difference between those cases.
Put differently: f(uint64(...)) is valid even if you only have one of f(uint64) or f(uint256) defined - that’s an ambiguity and this ambiguity is what the compiler complains about.

That being said, clearly in cases like this the compiler could choose the “most matching type”, so considering cases like this generally as ambiguities is stricter than necessary. We have several issues about this: overloading for implicitly convertible types · Issue #6498 · ethereum/solidity · GitHub and Ranked overload resolution · Issue #1256 · ethereum/solidity · GitHub

I would expect that changing the behaviour here will implicitly be a result of us redesigning and uniformizing the type system in preparation of moving to generic types, but I can’t promise a timeline on that right now.

3 Likes

Thanks for your answer and for linking to the existing issues about this.

It would indeed be great if the compiler chose the “most matching type” for us.

1 Like