In Solidity, it is valid to define functions of the same name, but with different parameter types. What is problematic is that in some situations, it is impossible to call such functions because of implicit conversions:
function f(uint64) { ... }
function f(uint32) { ... }
function test() {
uint16 a = 2;
f(a);
}
The call f(a)
will create a compiler error, because overload resolution first collects a set of possible functions purely by name. Then it eliminates all functions that cannot be called due to the argument types. If there is more than one function left (or no function at all), it is not clear which function to call.
In this case, the uint16 a can be both converted to uint32 and uint64.
This essentially leads to f(uint32) to be not callable at all, because any type that is implicitly convertible to uint32 is also implicitly convertible to uint64.
Another prominent example is having library functions that take “bytes memory” and “bytes storage” parameters.
We would like to solve this situation. There are at least two ways to solve it:
-
Disallow situations altogether where a function is not callable because it will always result in at least two functions being callable. This might be problematic because of the
using
statement that can import multiple functions from multiple places even though you do not really want to use those functions. -
Make such function calls work by modifying the overload resolution logic as follows:
If the set of function candidates contains a function whose parameter types exactly match the argument types, then this one is called. Otherwise, do overload resolution as it used to be: Check if there is a single function that can be called including implicit conversions of the arguments.
This way, it would be possible to call the right function above using f(uint32(a)) or f(uint64(a)).
A third option would be as follows: If there are multiple functions with the same name, always fail unless the argument and parameter types match exactly for one function.
The first and the third option are breaking changes because they can change the behaviour or validity of contracts that currently compile. The second option seems like it is a non-breaking change, because it only makes more contracts valid.
What do you think? What do you use overloads for? Would you find this feature useful or should it rather be restricted?
Thanks a lot for your feedback!