Repository: vyperlang/vyper

Issue:
Dynamics Arrays of `Enum` are no longer allowed
### Version Information

* vyper Version (output of `vyper --version`): 0.3.8+commit.1cd6f3db
* OS: OSX
* Python Version (output of `python --version`): 3.8.0

### What's your issue about?

In `v0.3.7`, Static Arrays of Enums were not allowed but Dynamics arrays could contain `Enum` types.
However, Dynamics Arrays of `Enum` are no longer allowed after #3156. 
```Vyper
enum Foo:
    a
bar: DynArray[Foo, 10]
```
Result of compilation with commit 1cd6f3db (with `v0.3.7`, it would compile succesfully) :

```Bash
Traceback (most recent call last):
  File "/Users/trocher/Documents/thesis/vyper/venv/bin/vyper", line 11, in <module>
    load_entry_point('vyper==0.3.8', 'console_scripts', 'vyper')()
  File "/Users/trocher/Documents/thesis/vyper/vyper/cli/vyper_compile.py", line 57, in _parse_cli_args
    return _parse_args(sys.argv[1:])
  File "/Users/trocher/Documents/thesis/vyper/vyper/cli/vyper_compile.py", line 154, in _parse_args
    compiled = compile_files(
  File "/Users/trocher/Documents/thesis/vyper/vyper/cli/vyper_compile.py", line 294, in compile_files
    compiler_data = vyper.compile_codes(
  File "/Users/trocher/Documents/thesis/vyper/vyper/evm/opcodes.py", line 226, in _wrapper
    return fn(*args, **kwargs)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/__init__.py", line 141, in compile_codes
    exc_handler(contract_name, exc)
  File "/Users/trocher/Documents/thesis/vyper/vyper/cli/vyper_compile.py", line 189, in exc_handler
    raise exception
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/__init__.py", line 138, in compile_codes
    out[contract_name][output_format] = OUTPUT_FORMATS[output_format](compiler_data)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/output.py", line 82, in build_ir_output
    return compiler_data.ir_nodes
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/phases.py", line 126, in ir_nodes
    ir, ir_runtime, sigs = self._ir_output
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/functools.py", line 966, in __get__
    val = self.func(instance)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/phases.py", line 122, in _ir_output
    return generate_ir_nodes(self.global_ctx, self.no_optimize)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/phases.py", line 117, in global_ctx
    return generate_global_context(self.vyper_module_folded, self.interface_codes)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/phases.py", line 107, in vyper_module_folded
    module, storage_layout = self._folded_module
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/functools.py", line 966, in __get__
    val = self.func(instance)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/phases.py", line 101, in _folded_module
    return generate_folded_ast(
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/phases.py", line 229, in generate_folded_ast
    validate_semantics(vyper_module_folded, interface_codes)
  File "/Users/trocher/Documents/thesis/vyper/vyper/semantics/analysis/__init__.py", line 12, in validate_semantics
    add_module_namespace(vyper_ast, interface_codes)
  File "/Users/trocher/Documents/thesis/vyper/vyper/semantics/analysis/module.py", line 43, in add_module_namespace
    ModuleNodeVisitor(vy_module, interface_codes, namespace)
  File "/Users/trocher/Documents/thesis/vyper/vyper/semantics/analysis/module.py", line 88, in __init__
    err_list.raise_if_not_empty()
  File "/Users/trocher/Documents/thesis/vyper/vyper/exceptions.py", line 18, in raise_if_not_empty
    raise self[0]
  File "/Users/trocher/Documents/thesis/vyper/vyper/semantics/analysis/module.py", line 76, in __init__
    self.visit(node)
  File "/Users/trocher/Documents/thesis/vyper/vyper/semantics/analysis/common.py", line 20, in visit
    visitor_fn(node, *args)
  File "/Users/trocher/Documents/thesis/vyper/vyper/semantics/analysis/module.py", line 186, in visit_VariableDecl
    type_ = type_from_annotation(node.annotation)
  File "/Users/trocher/Documents/thesis/vyper/vyper/semantics/types/utils.py", line 99, in type_from_annotation
    return type_ctor.from_annotation(node)
  File "/Users/trocher/Documents/thesis/vyper/vyper/semantics/types/subscriptable.py", line 266, in from_annotation
    raise StructureException(f"Arrays of {value_type} are not allowed", node)
vyper.exceptions.StructureException: Arrays of enum Foo('a') are not allowed
  contract "tests/customs/code.vy:5", line 5:5 
       4     a
  ---> 5 bar: DynArray[Foo, 10]
  ------------^
       6
```
### How can it be fixed?

If the intention is to keep the v0.3.7 semantics, my guess would be to implement a similar carveout as the one done for arrays of bytestrings since `Enum` does not have the `_as_array` field.
https://github.com/vyperlang/vyper/blob/1cd6f3db7e06ed97ffd9eefb9cfc67506e461544/vyper/semantics/types/bytestrings.py#L29-L31


Discussion:

Resolving patch:
diff --git a/vyper/codegen/expr.py b/vyper/codegen/expr.py
--- a/vyper/codegen/expr.py
+++ b/vyper/codegen/expr.py
@@ -198,7 +198,11 @@ def parse_Attribute(self):
         typ = self.expr._metadata["type"]
 
         # MyEnum.foo
-        if isinstance(typ, EnumT) and typ.name == self.expr.value.id:
+        if (
+            isinstance(typ, EnumT)
+            and isinstance(self.expr.value, vy_ast.Name)
+            and typ.name == self.expr.value.id
+        ):
             # 0, 1, 2, .. 255
             enum_id = typ._enum_members[self.expr.attr]
             value = 2 ** enum_id  # 0 => 0001, 1 => 0010, 2 => 0100, etc.
diff --git a/vyper/semantics/types/user.py b/vyper/semantics/types/user.py
--- a/vyper/semantics/types/user.py
+++ b/vyper/semantics/types/user.py
@@ -37,6 +37,9 @@ def __hash__(self):
 
 # note: enum behaves a lot like uint256, or uints in general.
 class EnumT(_UserType):
+    # this is a carveout because currently we allow dynamic arrays of
+    # enums, but not static arrays of enums
+    _as_darray = True
     _is_prim_word = True
 
     def __init__(self, name: str, members: dict) -> None:


Test patch:
diff --git a/tests/parser/syntax/test_dynamic_array.py b/tests/parser/syntax/test_dynamic_array.py
--- a/tests/parser/syntax/test_dynamic_array.py
+++ b/tests/parser/syntax/test_dynamic_array.py
@@ -1,5 +1,6 @@
 import pytest
 
+from vyper import compiler
 from vyper.exceptions import StructureException
 
 fail_list = [
@@ -21,3 +22,22 @@
 @pytest.mark.parametrize("bad_code,exc", fail_list)
 def test_block_fail(assert_compile_failed, get_contract, bad_code, exc):
     assert_compile_failed(lambda: get_contract(bad_code), exc)
+
+
+valid_list = [
+    """
+enum Foo:
+    FE
+    FI
+
+bar: DynArray[Foo, 10]
+    """,  # dynamic arrays of enums are allowed, but not static arrays
+    """
+bar: DynArray[Bytes[30], 10]
+    """,  # dynamic arrays of bytestrings are allowed, but not static arrays
+]
+
+
+@pytest.mark.parametrize("good_code", valid_list)
+def test_dynarray_pass(good_code):
+    assert compiler.compile_code(good_code) is not None
diff --git a/tests/parser/types/test_dynamic_array.py b/tests/parser/types/test_dynamic_array.py
--- a/tests/parser/types/test_dynamic_array.py
+++ b/tests/parser/types/test_dynamic_array.py
@@ -102,6 +102,11 @@ 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:
+    FOO
+    BAR
+
+y: DynArray[Foobar, 2]
 z: DynArray[int128, 2]
 
 @external
@@ -180,6 +185,20 @@ def qoo(inp: DynArray[int128, 2]) -> DynArray[DynArray[int128, 2], 2]:
 @external
 def roo(inp: DynArray[decimal, 2]) -> DynArray[DynArray[decimal, 2], 2]:
     return [inp, [3.0, 4.0]]
+
+@external
+def soo() -> DynArray[Foobar, 2]:
+    x: DynArray[Foobar, 2] = [Foobar.FOO, Foobar.BAR]
+    return x
+
+@external
+def too() -> DynArray[Foobar, 2]:
+    self.y = [Foobar.BAR, Foobar.FOO]
+    return self.y
+
+@external
+def uoo(inp: DynArray[Foobar, 2]) -> DynArray[DynArray[Foobar, 2], 2]:
+    return [inp, [Foobar.BAR, Foobar.FOO]]
     """
 
     c = get_contract_with_gas_estimation(list_output_tester_code)
@@ -200,6 +219,9 @@ def roo(inp: DynArray[decimal, 2]) -> DynArray[DynArray[decimal, 2], 2]:
     assert c.poo([[1, 2], [3, 4]]) == [[1, 2], [3, 4]]
     assert c.qoo([1, 2]) == [[1, 2], [3, 4]]
     assert c.roo([1, 2]) == [[1.0, 2.0], [3.0, 4.0]]
+    assert c.soo() == [1, 2]
+    assert c.too() == [2, 1]
+    assert c.uoo([1, 2]) == [[1, 2], [2, 1]]
 
     print("Passed list output tests")
 
@@ -508,25 +530,6 @@ def check2(a: address) -> bool:
     assert c.check2("0x0000000000000000000000000000000000000024") is False
 
 
-(
-    "bytes32",
-    [
-        [
-            b"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x80\\xac\\x58\\xca",  # noqa: E501
-            b"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x80\\xac\\x58\\xcb",  # noqa: E501
-        ],
-        [
-            b"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x80\\xac\\x58\\xcc",  # noqa: E501
-            b"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x80\\xac\\x58\\xcd",  # noqa: E501
-        ],
-    ],
-    [
-        b"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x80\\xac\\x58\\xcc",  # noqa: E501,
-        b"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x80\\xac\\x58\\xce",  # noqa: E501
-    ],
-),
-
-
 def test_member_in_nested_bytes32_list(get_contract_with_gas_estimation):
     code = """
 @external
@@ -1227,7 +1230,8 @@ def foo(x: {typ}) -> {typ}:
 
 @pytest.mark.parametrize("code_template,check_result", append_pop_complex_tests)
 @pytest.mark.parametrize(
-    "subtype", ["uint256[3]", "DynArray[uint256,3]", "DynArray[uint8, 4]", "Foo"]
+    "subtype",
+    ["uint256[3]", "DynArray[uint256,3]", "DynArray[uint8, 4]", "Foo", "DynArray[Foobar, 3]"],
 )
 # TODO change this to fuzz random data
 def test_append_pop_complex(get_contract, assert_tx_failed, code_template, check_result, subtype):
@@ -1242,6 +1246,15 @@ def test_append_pop_complex(get_contract, assert_tx_failed, code_template, check
     z: uint256
         """
         code = struct_def + "\n" + code
+    elif subtype == "DynArray[Foobar, 3]":
+        enum_def = """
+enum Foobar:
+    FOO
+    BAR
+    BAZ
+        """
+        code = enum_def + "\n" + code
+        test_data = [2 ** (i - 1) for i in test_data]
 
     c = get_contract(code)
     expected_result = check_result(test_data)
@@ -1279,20 +1292,27 @@ def foo() -> (uint256, DynArray[uint256, 3], DynArray[uint256, 2]):
 
 def test_list_of_structs_arg(get_contract):
     code = """
+enum Foobar:
+    FOO
+    BAR
+
 struct Foo:
     x: uint256
     y: uint256
+    z: Foobar
 
 @external
 def bar(_baz: DynArray[Foo, 3]) -> uint256:
     sum: uint256 = 0
     for i in range(3):
-        sum += _baz[i].x * _baz[i].y
+        e: Foobar = _baz[i].z
+        f: uint256 = convert(e, uint256)
+        sum += _baz[i].x * _baz[i].y + f
     return sum
     """
     c = get_contract(code)
-    c_input = [[x, y] for x, y in zip(range(3), range(3))]
-    assert c.bar(c_input) == 5  # 0 * 0 + 1 * 1 + 2 * 2
+    c_input = [[x, y, 1] for x, y in zip(range(3), range(3))]
+    assert c.bar(c_input) == 8  # (0 * 0 + 1) + (1 * 1 + 1) + (2 * 2 + 1)
 
 
 def test_list_of_structs_arg_with_dynamic_type(get_contract):
diff --git a/tests/parser/types/test_enum.py b/tests/parser/types/test_enum.py
--- a/tests/parser/types/test_enum.py
+++ b/tests/parser/types/test_enum.py
@@ -257,3 +257,22 @@ def baz(a: Roles) -> bool:
     assert c.baz(0b00001) is True  # Roles.USER should pass
     assert c.baz(0b00100) is True  # Roles.ADMIN should pass
     assert c.baz(0b01000) is False  # Roles.MANAGER should fail
+
+
+def test_struct_with_enum(get_contract_with_gas_estimation):
+    code = """
+enum Foobar:
+    FOO
+    BAR
+
+struct Foo:
+    a: uint256
+    b: Foobar
+
+@external
+def get_enum_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