Skip to content

Commit

Permalink
feat: replace enum with flag keyword (#3697)
Browse files Browse the repository at this point in the history
per title, replace `enum` with `flag` as it more closely models
https://docs.python.org/3/library/enum.html#enum.IntFlag than regular
enums. allow `enum` for now (for backwards compatibility) but convert to
`flag` internally and issue a warning
  • Loading branch information
AlbertoCentonze authored Dec 23, 2023
1 parent 8958bff commit 88c09a2
Show file tree
Hide file tree
Showing 24 changed files with 137 additions and 110 deletions.
22 changes: 11 additions & 11 deletions docs/types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -376,22 +376,22 @@ On the ABI level the Fixed-size bytes array is annotated as ``string``.
example_str: String[100] = "Test String"
Enums
Flags
-----

**Keyword:** ``enum``
**Keyword:** ``flag``

Enums are custom defined types. An enum must have at least one member, and can hold up to a maximum of 256 members.
Flags are custom defined types. A flag must have at least one member, and can hold up to a maximum of 256 members.
The members are represented by ``uint256`` values in the form of 2\ :sup:`n` where ``n`` is the index of the member in the range ``0 <= n <= 255``.

.. code-block:: python
# Defining an enum with two members
enum Roles:
# Defining a flag with two members
flag Roles:
ADMIN
USER
# Declaring an enum variable
# Declaring a flag variable
role: Roles = Roles.ADMIN
# Returning a member
Expand Down Expand Up @@ -426,13 +426,13 @@ Operator Description
``~x`` Bitwise not
============= ======================

Enum members can be combined using the above bitwise operators. While enum members have values that are power of two, enum member combinations may not.
Flag members can be combined using the above bitwise operators. While flag members have values that are power of two, flag member combinations may not.

The ``in`` and ``not in`` operators can be used in conjunction with enum member combinations to check for membership.
The ``in`` and ``not in`` operators can be used in conjunction with flag member combinations to check for membership.

.. code-block:: python
enum Roles:
flag Roles:
MANAGER
ADMIN
USER
Expand All @@ -447,7 +447,7 @@ The ``in`` and ``not in`` operators can be used in conjunction with enum member
def bar(a: Roles) -> bool:
return a not in (Roles.MANAGER | Roles.USER)
Note that ``in`` is not the same as strict equality (``==``). ``in`` checks that *any* of the flags on two enum objects are simultaneously set, while ``==`` checks that two enum objects are bit-for-bit equal.
Note that ``in`` is not the same as strict equality (``==``). ``in`` checks that *any* of the flags on two flag objects are simultaneously set, while ``==`` checks that two flag objects are bit-for-bit equal.

The following code uses bitwise operations to add and revoke permissions from a given ``Roles`` object.

Expand Down Expand Up @@ -488,7 +488,7 @@ Fixed-size Lists

Fixed-size lists hold a finite number of elements which belong to a specified type.

Lists can be declared with ``_name: _ValueType[_Integer]``, except ``Bytes[N]``, ``String[N]`` and enums.
Lists can be declared with ``_name: _ValueType[_Integer]``, except ``Bytes[N]``, ``String[N]`` and flags.

.. code-block:: python
Expand Down
8 changes: 4 additions & 4 deletions tests/functional/builtins/codegen/test_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,10 +486,10 @@ def test_memory_variable_convert(x: {i_typ}) -> {o_typ}:

@pytest.mark.parametrize("typ", ["uint8", "int128", "int256", "uint256"])
@pytest.mark.parametrize("val", [1, 2, 2**128, 2**256 - 1, 2**256 - 2])
def test_enum_conversion(get_contract_with_gas_estimation, assert_compile_failed, val, typ):
def test_flag_conversion(get_contract_with_gas_estimation, assert_compile_failed, val, typ):
roles = "\n ".join([f"ROLE_{i}" for i in range(256)])
contract = f"""
enum Roles:
flag Roles:
{roles}
@external
Expand All @@ -510,11 +510,11 @@ def bar(a: uint256) -> Roles:

@pytest.mark.parametrize("typ", ["uint8", "int128", "int256", "uint256"])
@pytest.mark.parametrize("val", [1, 2, 3, 4, 2**128, 2**256 - 1, 2**256 - 2])
def test_enum_conversion_2(
def test_flag_conversion_2(
get_contract_with_gas_estimation, assert_compile_failed, assert_tx_failed, val, typ
):
contract = f"""
enum Status:
flag Status:
STARTED
PAUSED
STOPPED
Expand Down
4 changes: 2 additions & 2 deletions tests/functional/codegen/features/test_assignment.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def bar(x: {typ}) -> {typ}:

def test_internal_assign_struct(get_contract_with_gas_estimation):
code = """
enum Bar:
flag Bar:
BAD
BAK
BAZ
Expand All @@ -92,7 +92,7 @@ def bar(x: Foo) -> Foo:

def test_internal_assign_struct_member(get_contract_with_gas_estimation):
code = """
enum Bar:
flag Bar:
BAD
BAK
BAZ
Expand Down
8 changes: 4 additions & 4 deletions tests/functional/codegen/features/test_clampers.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,9 @@ def foo(s: bool) -> bool:

@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS))
@pytest.mark.parametrize("value", [0] + [2**i for i in range(5)])
def test_enum_clamper_passing(w3, get_contract, value, evm_version):
def test_flag_clamper_passing(w3, get_contract, value, evm_version):
code = """
enum Roles:
flag Roles:
USER
STAFF
ADMIN
Expand All @@ -207,9 +207,9 @@ def foo(s: Roles) -> Roles:

@pytest.mark.parametrize("evm_version", list(EVM_VERSIONS))
@pytest.mark.parametrize("value", [2**i for i in range(5, 256)])
def test_enum_clamper_failing(w3, assert_tx_failed, get_contract, value, evm_version):
def test_flag_clamper_failing(w3, assert_tx_failed, get_contract, value, evm_version):
code = """
enum Roles:
flag Roles:
USER
STAFF
ADMIN
Expand Down
10 changes: 5 additions & 5 deletions tests/functional/codegen/types/test_dynamic_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def foo6() -> DynArray[DynArray[String[32], 2], 2]:

def test_list_output_tester_code(get_contract_with_gas_estimation):
list_output_tester_code = """
enum Foobar:
flag Foobar:
FOO
BAR
Expand Down Expand Up @@ -1247,13 +1247,13 @@ def test_append_pop_complex(get_contract, assert_tx_failed, code_template, check
"""
code = struct_def + "\n" + code
elif subtype == "DynArray[Foobar, 3]":
enum_def = """
enum Foobar:
flag_def = """
flag Foobar:
FOO
BAR
BAZ
"""
code = enum_def + "\n" + code
code = flag_def + "\n" + code
test_data = [2 ** (i - 1) for i in test_data]

c = get_contract(code)
Expand Down Expand Up @@ -1292,7 +1292,7 @@ def foo() -> (uint256, DynArray[uint256, 3], DynArray[uint256, 2]):

def test_list_of_structs_arg(get_contract):
code = """
enum Foobar:
flag Foobar:
FOO
BAR
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
def test_values_should_be_increasing_ints(get_contract):
code = """
enum Action:
flag Action:
BUY
SELL
CANCEL
Expand All @@ -26,9 +26,9 @@ def cancel() -> Action:
assert c.cancel() == 4


def test_enum_storage(get_contract):
def test_flag_storage(get_contract):
code = """
enum Actions:
flag Actions:
BUY
SELL
CANCEL
Expand All @@ -49,7 +49,7 @@ def set_and_get(a: Actions) -> Actions:

def test_eq_neq(get_contract):
code = """
enum Roles:
flag Roles:
USER
STAFF
ADMIN
Expand All @@ -76,7 +76,7 @@ def is_not_boss(a: Roles) -> bool:

def test_bitwise(get_contract, assert_tx_failed):
code = """
enum Roles:
flag Roles:
USER
STAFF
ADMIN
Expand Down Expand Up @@ -147,7 +147,7 @@ def binv_arg(a: Roles) -> Roles:

def test_augassign_storage(get_contract, w3, assert_tx_failed):
code = """
enum Roles:
flag Roles:
ADMIN
MINTER
Expand Down Expand Up @@ -214,9 +214,9 @@ def checkMinter(minter: address):
assert_tx_failed(lambda: c.checkMinter(admin_address))


def test_in_enum(get_contract_with_gas_estimation):
def test_in_flag(get_contract_with_gas_estimation):
code = """
enum Roles:
flag Roles:
USER
STAFF
ADMIN
Expand Down Expand Up @@ -259,9 +259,9 @@ def baz(a: Roles) -> bool:
assert c.baz(0b01000) is False # Roles.MANAGER should fail


def test_struct_with_enum(get_contract_with_gas_estimation):
def test_struct_with_flag(get_contract_with_gas_estimation):
code = """
enum Foobar:
flag Foobar:
FOO
BAR
Expand All @@ -270,17 +270,17 @@ def test_struct_with_enum(get_contract_with_gas_estimation):
b: Foobar
@external
def get_enum_from_struct() -> Foobar:
def get_flag_from_struct() -> Foobar:
f: Foo = Foo({a: 1, b: Foobar.BAR})
return f.b
"""
c = get_contract_with_gas_estimation(code)
assert c.get_enum_from_struct() == 2
assert c.get_flag_from_struct() == 2


def test_mapping_with_enum(get_contract_with_gas_estimation):
def test_mapping_with_flag(get_contract_with_gas_estimation):
code = """
enum Foobar:
flag Foobar:
FOO
BAR
Expand Down
4 changes: 2 additions & 2 deletions tests/functional/syntax/test_dynamic_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ def test_block_fail(assert_compile_failed, get_contract, bad_code, exc):

valid_list = [
"""
enum Foo:
flag Foo:
FE
FI
bar: DynArray[Foo, 10]
""", # dynamic arrays of enums are allowed, but not static arrays
""", # dynamic arrays of flags are allowed, but not static arrays
"""
bar: DynArray[Bytes[30], 10]
""", # dynamic arrays of bytestrings are allowed, but not static arrays
Expand Down
Loading

0 comments on commit 88c09a2

Please sign in to comment.