Add implicit convertibility to function pointer with higher state mutability

This commit is contained in:
Jesse Busman 2018-06-28 13:31:23 +02:00 committed by chriseth
parent 414559bd07
commit c059119145
7 changed files with 263 additions and 23 deletions

View File

@ -426,8 +426,26 @@ function type should not return anything, the whole ``returns (<return types>)``
part has to be omitted. part has to be omitted.
By default, function types are internal, so the ``internal`` keyword can be By default, function types are internal, so the ``internal`` keyword can be
omitted. In contrast, contract functions themselves are public by default, omitted. Note that this only applies to function types. Visibility has
only when used as the name of a type, the default is internal. to be specified explicitly for functions defined in contracts, they
do not have a default.
A function type ``A`` is implicitly convertible to a function type ``B`` if and only if
their parameter types are identical, their return types are identical,
their internal/external property is identical and the state mutability of ``A``
is not more restrictive than the state mutability of ``B``. In particular:
- ``pure`` functions can be converted to ``view`` and ``non-payable`` functions
- ``view`` functions can be converted to ``non-payable`` functions
- ``payable`` functions can be converted to ``non-payable`` functions
No other conversions are possible.
The rule about ``payable`` and ``non-payable`` might be a little
confusing, but in essence, if a function is ``payable``, this means that it
also accepts a payment of zero Ether, so it also is ``non-payable``.
On the other hand, a ``non-payable`` function will reject Ether sent to it,
so ``non-payable`` functions cannot be converted to ``payable`` functions.
If a function type variable is not initialized, calling it will result If a function type variable is not initialized, calling it will result
in an exception. The same happens if you call a function after using ``delete`` in an exception. The same happens if you call a function after using ``delete``

View File

@ -2569,28 +2569,10 @@ bool FunctionType::operator==(Type const& _other) const
{ {
if (_other.category() != category()) if (_other.category() != category())
return false; return false;
FunctionType const& other = dynamic_cast<FunctionType const&>(_other); FunctionType const& other = dynamic_cast<FunctionType const&>(_other);
if ( if (!equalExcludingStateMutability(other))
m_kind != other.m_kind ||
m_stateMutability != other.stateMutability() ||
m_parameterTypes.size() != other.m_parameterTypes.size() ||
m_returnParameterTypes.size() != other.m_returnParameterTypes.size()
)
return false; return false;
if (m_stateMutability != other.stateMutability())
auto typeCompare = [](TypePointer const& _a, TypePointer const& _b) -> bool { return *_a == *_b; };
if (
!equal(m_parameterTypes.cbegin(), m_parameterTypes.cend(), other.m_parameterTypes.cbegin(), typeCompare) ||
!equal(m_returnParameterTypes.cbegin(), m_returnParameterTypes.cend(), other.m_returnParameterTypes.cbegin(), typeCompare)
)
return false;
//@todo this is ugly, but cannot be prevented right now
if (m_gasSet != other.m_gasSet || m_valueSet != other.m_valueSet)
return false;
if (bound() != other.bound())
return false;
if (bound() && *selfType() != *other.selfType())
return false; return false;
return true; return true;
} }
@ -2606,6 +2588,31 @@ bool FunctionType::isExplicitlyConvertibleTo(Type const& _convertTo) const
return _convertTo.category() == category(); return _convertTo.category() == category();
} }
bool FunctionType::isImplicitlyConvertibleTo(Type const& _convertTo) const
{
if (_convertTo.category() != category())
return false;
FunctionType const& convertTo = dynamic_cast<FunctionType const&>(_convertTo);
if (!equalExcludingStateMutability(convertTo))
return false;
// non-payable should not be convertible to payable
if (m_stateMutability != StateMutability::Payable && convertTo.stateMutability() == StateMutability::Payable)
return false;
// payable should be convertible to non-payable, because you are free to pay 0 ether
if (m_stateMutability == StateMutability::Payable && convertTo.stateMutability() == StateMutability::NonPayable)
return true;
// e.g. pure should be convertible to view, but not the other way around.
if (m_stateMutability > convertTo.stateMutability())
return false;
return true;
}
TypePointer FunctionType::unaryOperatorResult(Token::Value _operator) const TypePointer FunctionType::unaryOperatorResult(Token::Value _operator) const
{ {
if (_operator == Token::Value::Delete) if (_operator == Token::Value::Delete)
@ -2863,6 +2870,38 @@ bool FunctionType::hasEqualParameterTypes(FunctionType const& _other) const
); );
} }
bool FunctionType::hasEqualReturnTypes(FunctionType const& _other) const
{
if (m_returnParameterTypes.size() != _other.m_returnParameterTypes.size())
return false;
return equal(
m_returnParameterTypes.cbegin(),
m_returnParameterTypes.cend(),
_other.m_returnParameterTypes.cbegin(),
[](TypePointer const& _a, TypePointer const& _b) -> bool { return *_a == *_b; }
);
}
bool FunctionType::equalExcludingStateMutability(FunctionType const& _other) const
{
if (m_kind != _other.m_kind)
return false;
if (!hasEqualParameterTypes(_other) || !hasEqualReturnTypes(_other))
return false;
//@todo this is ugly, but cannot be prevented right now
if (m_gasSet != _other.m_gasSet || m_valueSet != _other.m_valueSet)
return false;
if (bound() != _other.bound())
return false;
solAssert(!bound() || *selfType() == *_other.selfType(), "");
return true;
}
bool FunctionType::isBareCall() const bool FunctionType::isBareCall() const
{ {
switch (m_kind) switch (m_kind)

View File

@ -1012,6 +1012,7 @@ public:
virtual std::string richIdentifier() const override; virtual std::string richIdentifier() const override;
virtual bool operator==(Type const& _other) const override; virtual bool operator==(Type const& _other) const override;
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override; virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override;
@ -1041,8 +1042,12 @@ public:
/// @param _selfType if the function is bound, this has to be supplied and is the type of the /// @param _selfType if the function is bound, this has to be supplied and is the type of the
/// expression the function is called on. /// expression the function is called on.
bool canTakeArguments(TypePointers const& _arguments, TypePointer const& _selfType = TypePointer()) const; bool canTakeArguments(TypePointers const& _arguments, TypePointer const& _selfType = TypePointer()) const;
/// @returns true if the types of parameters are equal (doesn't check return parameter types) /// @returns true if the types of parameters are equal (does not check return parameter types)
bool hasEqualParameterTypes(FunctionType const& _other) const; bool hasEqualParameterTypes(FunctionType const& _other) const;
/// @returns true iff the return types are equal (does not check parameter types)
bool hasEqualReturnTypes(FunctionType const& _other) const;
/// @returns true iff the function type is equal to the given type, ignoring state mutability differences.
bool equalExcludingStateMutability(FunctionType const& _other) const;
/// @returns true if the ABI is used for this call (only meaningful for external calls) /// @returns true if the ABI is used for this call (only meaningful for external calls)
bool isBareCall() const; bool isBareCall() const;

View File

@ -0,0 +1,42 @@
contract Test
{
function uint256_to_uint256(uint256 x) internal pure returns (uint256) { return x; }
function uint256_to_string(uint256 x) internal pure returns (string memory) { return x == 0 ? "a" : "b"; }
function uint256_to_string_storage(uint256) internal pure returns (string storage);
function string_to_uint256(string memory x) internal pure returns (uint256) { return bytes(x).length; }
function string_to_string(string memory x) internal pure returns (string memory) { return x; }
function uint256_uint256_to_uint256(uint256 x, uint256 y) internal pure returns (uint256) { return x + y; }
function uint256_uint256_to_string(uint256 x, uint256 y) internal pure returns (string memory) { return x == y ? "a" : "b"; }
function string_uint256_to_string(string memory x, uint256 y) internal pure returns (string memory) { return y == 0 ? "a" : x; }
function string_string_to_string(string memory x, string memory y) internal pure returns (string memory) { return bytes(x).length == 0 ? y : x; }
function uint256_string_to_string(uint256 x, string memory y) internal pure returns (string memory) { return x == 0 ? "a" : y; }
function tests() internal pure
{
function (uint256) internal pure returns (uint256) var_uint256_to_uint256 = uint256_to_string;
function (uint256) internal pure returns (string memory) var_uint256_to_string = uint256_to_string_storage;
function (string memory) internal pure returns (uint256) var_string_to_uint256 = uint256_to_string;
function (string memory) internal pure returns (string memory) var_string_to_string = var_uint256_to_string;
function (uint256, uint256) internal pure returns (uint256) var_uint256_uint256_to_uint256 = uint256_to_uint256;
function (string memory, uint256) internal pure returns (string memory) var_string_uint256_to_string = string_to_string;
function (string memory, string memory) internal pure returns (string memory) var_string_string_to_string = string_to_string;
var_uint256_to_uint256(1);
var_uint256_to_string(2);
var_string_to_uint256("a");
var_string_to_string("b");
var_uint256_uint256_to_uint256(3, 4);
var_string_uint256_to_string("c", 7);
var_string_string_to_string("d", "e");
}
}
// ----
// TypeError: (1218-1311): Type function (uint256) pure returns (string memory) is not implicitly convertible to expected type function (uint256) pure returns (uint256).
// TypeError: (1319-1425): Type function (uint256) pure returns (string storage pointer) is not implicitly convertible to expected type function (uint256) pure returns (string memory).
// TypeError: (1433-1531): Type function (uint256) pure returns (string memory) is not implicitly convertible to expected type function (string memory) pure returns (uint256).
// TypeError: (1539-1646): Type function (uint256) pure returns (string memory) is not implicitly convertible to expected type function (string memory) pure returns (string memory).
// TypeError: (1655-1766): Type function (uint256) pure returns (uint256) is not implicitly convertible to expected type function (uint256,uint256) pure returns (uint256).
// TypeError: (1774-1893): Type function (string memory) pure returns (string memory) is not implicitly convertible to expected type function (string memory,uint256) pure returns (string memory).
// TypeError: (1901-2025): Type function (string memory) pure returns (string memory) is not implicitly convertible to expected type function (string memory,string memory) pure returns (string memory).

View File

@ -0,0 +1,39 @@
contract Test
{
function uint256_to_uint256(uint256 x) internal pure returns (uint256) { return x; }
function uint256_to_string(uint256 x) internal pure returns (string memory) { return x == 0 ? "a" : "b"; }
function string_to_uint256(string memory x) internal pure returns (uint256) { return bytes(x).length; }
function string_to_string(string memory x) internal pure returns (string memory) { return x; }
function uint256_uint256_to_uint256(uint256 x, uint256 y) internal pure returns (uint256) { return x + y; }
function uint256_uint256_to_string(uint256 x, uint256 y) internal pure returns (string memory) { return x == y ? "a" : "b"; }
function string_uint256_to_string(string memory x, uint256 y) internal pure returns (string memory) { return y == 0 ? "a" : x; }
function string_string_to_string(string memory x, string memory y) internal pure returns (string memory) { return bytes(x).length == 0 ? y : x; }
function uint256_string_to_string(uint256 x, string memory y) internal pure returns (string memory) { return x == 0 ? "a" : y; }
function tests() internal pure
{
function (uint256) internal pure returns (uint256) var_uint256_to_uint256 = uint256_to_uint256;
function (uint256) internal pure returns (string memory) var_uint256_to_string = uint256_to_string;
function (string memory) internal pure returns (uint256) var_string_to_uint256 = string_to_uint256;
function (string memory) internal pure returns (string memory) var_string_to_string = string_to_string;
function (uint256, uint256) internal pure returns (uint256) var_uint256_uint256_to_uint256 = uint256_uint256_to_uint256;
function (uint256, uint256) internal pure returns (string memory) var_uint256_uint256_to_string = uint256_uint256_to_string;
function (string memory, uint256) internal pure returns (string memory) var_string_uint256_to_string = string_uint256_to_string;
function (string memory, string memory) internal pure returns (string memory) var_string_string_to_string = string_string_to_string;
function (uint256, string memory) internal pure returns (string memory) var_uint256_string_to_string = uint256_string_to_string;
// Avoid unused variable warnings:
var_uint256_to_uint256(1);
var_uint256_to_string(2);
var_string_to_uint256("a");
var_string_to_string("b");
var_uint256_uint256_to_uint256(3, 4);
var_uint256_uint256_to_string(5, 6);
var_string_uint256_to_string("c", 7);
var_string_string_to_string("d", "e");
var_uint256_string_to_string(8, "f");
}
}
// ----

View File

@ -0,0 +1,51 @@
contract Test
{
function internalPureFunc(uint256 x) internal pure returns (uint256) { return x; }
function internalViewFunc(uint256 x) internal view returns (uint256) { return x; }
function internalMutableFunc(uint256 x) internal returns (uint256) { return x; }
function externalPureFunc(uint256 x) external pure returns (uint256) { return x; }
function externalViewFunc(uint256 x) external view returns (uint256) { return x; }
function externalPayableFunc(uint256 x) external payable returns (uint256) { return x; }
function externalMutableFunc(uint256 x) external returns (uint256) { return x; }
function funcTakesInternalPure(function(uint256) internal pure returns(uint256) a) internal returns (uint256) { return a(4); }
function funcTakesInternalView(function(uint256) internal view returns(uint256) a) internal returns (uint256) { return a(4); }
function funcTakesInternalMutable(function(uint256) internal returns(uint256) a) internal returns (uint256) { return a(4); }
function funcTakesExternalPure(function(uint256) external pure returns(uint256) a) internal returns (uint256) { return a(4); }
function funcTakesExternalView(function(uint256) external view returns(uint256) a) internal returns (uint256) { return a(4); }
function funcTakesExternalPayable(function(uint256) external payable returns(uint256) a) internal returns (uint256) { return a(4); }
function funcTakesExternalMutable(function(uint256) external returns(uint256) a) internal returns (uint256) { return a(4); }
function tests() internal
{
funcTakesInternalPure(internalViewFunc); // view -> pure should fail
funcTakesInternalPure(internalMutableFunc); // mutable -> pure should fail
funcTakesInternalView(internalMutableFunc); // mutable -> view should fail
funcTakesExternalPure(this.externalViewFunc); // view -> pure should fail
funcTakesExternalPure(this.externalPayableFunc); // payable -> pure should fail
funcTakesExternalPure(this.externalMutableFunc); // mutable -> pure should fail
funcTakesExternalView(this.externalPayableFunc); // payable -> view should fail
funcTakesExternalView(this.externalMutableFunc); // mutable -> view should fail
funcTakesExternalPayable(this.externalPureFunc); // pure -> payable should fail
funcTakesExternalPayable(this.externalViewFunc); // view -> payable should fail
funcTakesExternalPayable(this.externalMutableFunc); // mutable -> payable should fail
}
}
// ----
// TypeError: (1580-1596): Invalid type for argument in function call. Invalid implicit conversion from function (uint256) view returns (uint256) to function (uint256) pure returns (uint256) requested.
// TypeError: (1653-1672): Invalid type for argument in function call. Invalid implicit conversion from function (uint256) returns (uint256) to function (uint256) pure returns (uint256) requested.
// TypeError: (1733-1752): Invalid type for argument in function call. Invalid implicit conversion from function (uint256) returns (uint256) to function (uint256) view returns (uint256) requested.
// TypeError: (1813-1834): Invalid type for argument in function call. Invalid implicit conversion from function (uint256) view external returns (uint256) to function (uint256) pure external returns (uint256) requested.
// TypeError: (1891-1915): Invalid type for argument in function call. Invalid implicit conversion from function (uint256) payable external returns (uint256) to function (uint256) pure external returns (uint256) requested.
// TypeError: (1975-1999): Invalid type for argument in function call. Invalid implicit conversion from function (uint256) external returns (uint256) to function (uint256) pure external returns (uint256) requested.
// TypeError: (2060-2084): Invalid type for argument in function call. Invalid implicit conversion from function (uint256) payable external returns (uint256) to function (uint256) view external returns (uint256) requested.
// TypeError: (2144-2168): Invalid type for argument in function call. Invalid implicit conversion from function (uint256) external returns (uint256) to function (uint256) view external returns (uint256) requested.
// TypeError: (2232-2253): Invalid type for argument in function call. Invalid implicit conversion from function (uint256) pure external returns (uint256) to function (uint256) payable external returns (uint256) requested.
// TypeError: (2316-2337): Invalid type for argument in function call. Invalid implicit conversion from function (uint256) view external returns (uint256) to function (uint256) payable external returns (uint256) requested.
// TypeError: (2400-2424): Invalid type for argument in function call. Invalid implicit conversion from function (uint256) external returns (uint256) to function (uint256) payable external returns (uint256) requested.

View File

@ -0,0 +1,46 @@
contract Test
{
uint y;
function internalPureFunc(uint256 x) internal pure returns (uint256) { return x; }
function internalViewFunc(uint256 x) internal view returns (uint256) { return x + y; }
function internalMutableFunc(uint256 x) internal returns (uint256) { y = x; return x; }
function externalPureFunc(uint256 x) external pure returns (uint256) { return x; }
function externalViewFunc(uint256 x) external view returns (uint256) { return x + y; }
function externalPayableFunc(uint256 x) external payable returns (uint256) { return x + y; }
function externalMutableFunc(uint256 x) external returns (uint256) { y = x; return x; }
function funcTakesInternalPure(function(uint256) internal pure returns(uint256) a) internal pure returns (uint256) { return a(4); }
function funcTakesInternalView(function(uint256) internal view returns(uint256) a) internal view returns (uint256) { return a(4); }
function funcTakesInternalMutable(function(uint256) internal returns(uint256) a) internal returns (uint256) { return a(4); }
function funcTakesExternalPure(function(uint256) external pure returns(uint256) a) internal pure returns (uint256) { return a(4); }
function funcTakesExternalView(function(uint256) external view returns(uint256) a) internal view returns (uint256) { return a(4); }
function funcTakesExternalPayable(function(uint256) external payable returns(uint256) a) internal returns (uint256) { return a(4); }
function funcTakesExternalMutable(function(uint256) external returns(uint256) a) internal returns (uint256) { return a(4); }
function tests() internal
{
funcTakesInternalPure(internalPureFunc);
funcTakesInternalView(internalPureFunc);
funcTakesInternalView(internalViewFunc);
funcTakesInternalMutable(internalPureFunc);
funcTakesInternalMutable(internalViewFunc);
funcTakesInternalMutable(internalMutableFunc);
funcTakesExternalPure(this.externalPureFunc);
funcTakesExternalView(this.externalPureFunc);
funcTakesExternalView(this.externalViewFunc);
funcTakesExternalPayable(this.externalPayableFunc);
funcTakesExternalMutable(this.externalPureFunc);
funcTakesExternalMutable(this.externalViewFunc);
funcTakesExternalMutable(this.externalPayableFunc);
funcTakesExternalMutable(this.externalMutableFunc);
}
}
// ----