Merge pull request #11847 from ethereum/enum-min-max-11469

Add type().min/max for enums
This commit is contained in:
chriseth 2021-09-01 15:43:05 +02:00 committed by GitHub
commit 29cc7aa951
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 85 additions and 25 deletions

View File

@ -2,6 +2,7 @@
Language Features:
* Inheritance: A function that overrides only a single interface function does not require the ``override`` specifier.
* Type System: Support ``type().min`` and ``type().max`` for enums.
Compiler Features:

View File

@ -587,11 +587,14 @@ Enums cannot have more than 256 members.
The data representation is the same as for enums in C: The options are represented by
subsequent unsigned integer values starting from ``0``.
Using ``type(NameOfEnum).min`` and ``type(NameOfEnum).max`` you can get the
smallest and respectively largest value of the given enum.
.. code-block:: solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
pragma solidity ^0.8.8;
contract test {
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
@ -612,6 +615,14 @@ subsequent unsigned integer values starting from ``0``.
function getDefaultChoice() public pure returns (uint) {
return uint(defaultChoice);
}
function getLargestValue() public pure returns (ActionChoices) {
return type(ActionChoices).max;
}
function getSmallestValue() public pure returns (ActionChoices) {
return type(ActionChoices).min;
}
}
.. note::

View File

@ -246,7 +246,10 @@ TypePointers TypeChecker::typeCheckMetaTypeFunctionAndRetrieveReturnType(Functio
Type::Category typeCategory = typeTypePtr->actualType()->category();
if (auto const* contractType = dynamic_cast<ContractType const*>(typeTypePtr->actualType()))
wrongType = contractType->isSuper();
else if (typeCategory != Type::Category::Integer)
else if (
typeCategory != Type::Category::Integer &&
typeCategory != Type::Category::Enum
)
wrongType = true;
}
else
@ -257,7 +260,7 @@ TypePointers TypeChecker::typeCheckMetaTypeFunctionAndRetrieveReturnType(Functio
4259_error,
arguments.front()->location(),
"Invalid type for argument in the function call. "
"A contract type or an integer type is required, but " +
"An enum type, contract type or an integer type is required, but " +
type(*arguments.front())->toString(true) + " provided."
);

View File

@ -566,9 +566,10 @@ MagicType const* TypeProvider::meta(Type const* _type)
solAssert(
_type && (
_type->category() == Type::Category::Contract ||
_type->category() == Type::Category::Integer
_type->category() == Type::Category::Integer ||
_type->category() == Type::Category::Enum
),
"Only contracts or integer types supported for now."
"Only enum, contracts or integer types supported for now."
);
return createAndGet<MagicType>(_type);
}

View File

@ -3961,9 +3961,10 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const
solAssert(
m_typeArgument && (
m_typeArgument->category() == Type::Category::Contract ||
m_typeArgument->category() == Type::Category::Integer
m_typeArgument->category() == Type::Category::Integer ||
m_typeArgument->category() == Type::Category::Enum
),
"Only contracts or integer types supported for now"
"Only enums, contracts or integer types supported for now"
);
if (m_typeArgument->category() == Type::Category::Contract)
@ -3989,6 +3990,14 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const
{"max", integerTypePointer},
});
}
else if (m_typeArgument->category() == Type::Category::Enum)
{
EnumType const* enumTypePointer = dynamic_cast<EnumType const*>(m_typeArgument);
return MemberList::MemberMap({
{"min", enumTypePointer},
{"max", enumTypePointer},
});
}
}
}
solAssert(false, "Unknown kind of magic.");

View File

@ -1071,6 +1071,12 @@ public:
/// @returns the value that the string has in the Enum
unsigned int memberValue(ASTString const& _member) const;
size_t numberOfMembers() const;
unsigned int minValue() const { return 0; }
unsigned int maxValue() const
{
solAssert(numberOfMembers() <= 256, "");
return static_cast<unsigned int>(numberOfMembers()) - 1;
}
private:
EnumDefinition const& m_enum;

View File

@ -1784,12 +1784,13 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
else if (member == "min" || member == "max")
{
MagicType const* arg = dynamic_cast<MagicType const*>(_memberAccess.expression().annotation().type);
IntegerType const* integerType = dynamic_cast<IntegerType const*>(arg->typeArgument());
if (member == "min")
m_context << integerType->min();
if (IntegerType const* integerType = dynamic_cast<IntegerType const*>(arg->typeArgument()))
m_context << (member == "min" ? integerType->min() : integerType->max());
else if (EnumType const* enumType = dynamic_cast<EnumType const*>(arg->typeArgument()))
m_context << (member == "min" ? enumType->minValue() : enumType->maxValue());
else
m_context << integerType->max();
solAssert(false, "min/max not available for the given type.");
}
else if ((set<string>{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}).count(member))
{

View File

@ -1785,12 +1785,26 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
else if (member == "min" || member == "max")
{
MagicType const* arg = dynamic_cast<MagicType const*>(_memberAccess.expression().annotation().type);
IntegerType const* integerType = dynamic_cast<IntegerType const*>(arg->typeArgument());
if (member == "min")
define(_memberAccess) << formatNumber(integerType->min()) << "\n";
string requestedValue;
if (IntegerType const* integerType = dynamic_cast<IntegerType const*>(arg->typeArgument()))
{
if (member == "min")
requestedValue = formatNumber(integerType->min());
else
requestedValue = formatNumber(integerType->max());
}
else if (EnumType const* enumType = dynamic_cast<EnumType const*>(arg->typeArgument()))
{
if (member == "min")
requestedValue = to_string(enumType->minValue());
else
requestedValue = to_string(enumType->maxValue());
}
else
define(_memberAccess) << formatNumber(integerType->max()) << "\n";
solAssert(false, "min/max requested on unexpected type.");
define(_memberAccess) << requestedValue << "\n";
}
else if (set<string>{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}.count(member))
{

View File

@ -1284,8 +1284,10 @@ bool SMTEncoder::visit(MemberAccess const& _memberAccess)
auto const& memberName = _memberAccess.memberName();
if (memberName == "min" || memberName == "max")
{
IntegerType const& integerType = dynamic_cast<IntegerType const&>(*magicType->typeArgument());
defineExpr(_memberAccess, memberName == "min" ? integerType.minValue() : integerType.maxValue());
if (IntegerType const* integerType = dynamic_cast<IntegerType const*>(magicType->typeArgument()))
defineExpr(_memberAccess, memberName == "min" ? integerType->minValue() : integerType->maxValue());
else if (EnumType const* enumType = dynamic_cast<EnumType const*>(magicType->typeArgument()))
defineExpr(_memberAccess, memberName == "min" ? enumType->minValue() : enumType->maxValue());
}
else if (memberName == "interfaceId")
{

View File

@ -0,0 +1,12 @@
contract test {
enum MinMax { A, B, C, D }
function min() public returns(uint) { return uint(type(MinMax).min); }
function max() public returns(uint) { return uint(type(MinMax).max); }
}
// ====
// compileViaYul: also
// ----
// min() -> 0
// max() -> 3

View File

@ -5,4 +5,4 @@ contract C {
}
}
// ----
// TypeError 4259: (93-98): Invalid type for argument in the function call. A contract type or an integer type is required, but type(bytes) provided.
// TypeError 4259: (93-98): Invalid type for argument in the function call. An enum type, contract type or an integer type is required, but type(bytes) provided.

View File

@ -10,4 +10,4 @@ contract SuperTest is Other {
}
}
// ----
// TypeError 4259: (177-182): Invalid type for argument in the function call. A contract type or an integer type is required, but type(contract super SuperTest) provided.
// TypeError 4259: (177-182): Invalid type for argument in the function call. An enum type, contract type or an integer type is required, but type(contract super SuperTest) provided.

View File

@ -14,4 +14,4 @@ abstract contract Test is ERC165 {
}
}
// ----
// TypeError 4259: (592-597): Invalid type for argument in the function call. A contract type or an integer type is required, but type(contract super Test) provided.
// TypeError 4259: (592-597): Invalid type for argument in the function call. An enum type, contract type or an integer type is required, but type(contract super Test) provided.

View File

@ -50,4 +50,4 @@ contract D is B, C {
}
}
// ----
// TypeError 4259: (426-431): Invalid type for argument in the function call. A contract type or an integer type is required, but type(contract super B) provided.
// TypeError 4259: (426-431): Invalid type for argument in the function call. An enum type, contract type or an integer type is required, but type(contract super B) provided.

View File

@ -4,4 +4,4 @@ contract Test {
}
}
// ----
// TypeError 4259: (65-75): Invalid type for argument in the function call. A contract type or an integer type is required, but type(contract Test) provided.
// TypeError 4259: (65-75): Invalid type for argument in the function call. An enum type, contract type or an integer type is required, but type(contract Test) provided.

View File

@ -6,4 +6,4 @@ contract Test {
}
}
// ----
// TypeError 4259: (154-155): Invalid type for argument in the function call. A contract type or an integer type is required, but type(struct Test.S) provided.
// TypeError 4259: (154-155): Invalid type for argument in the function call. An enum type, contract type or an integer type is required, but type(struct Test.S) provided.

View File

@ -14,4 +14,4 @@ contract C {
// ----
// TypeError 5347: (72-76): Try can only be used with external function calls and contract creation calls.
// TypeError 2536: (119-128): Try can only be used with external function calls and contract creation calls.
// TypeError 4259: (176-183): Invalid type for argument in the function call. A contract type or an integer type is required, but type(address) provided.
// TypeError 4259: (176-183): Invalid type for argument in the function call. An enum type, contract type or an integer type is required, but type(address) provided.