Repository: vyperlang/vyper

Issue:
Dynamic array of `HashMap`s allowed but unusable
### Version Information

* vyper Version (output of `vyper --version`): 0.3.8+commit.6020b8bb
* OS: OSX
* Python Version (output of `python --version`): 3.8.0

### What's your issue about?

While it is possible to declare a storage variable of type `DynArray[HashMap[A,B],n]` where `A` and `B` are valid types for the keys and values of `HashMap` and `n` is a positive integer, it seems impossible to compile a contract that update, append or read elements of such array. Moreover, having it as `public` will lead the compiler to fail as it cannot create a getter that takes a `HashMap` as argument.

The following contract will compile:
``` Vyper
foo: DynArray[HashMap[uint8,uint8], 2]
```
While the compiler will output `vyper.exceptions.InvalidType: Maps are not supported for function arguments or outputs` when trying to compile one of the following contracts
``` Vyper
foo: public(DynArray[HashMap[uint8,uint8], 2])
```
``` Vyper
foo: DynArray[HashMap[uint8,uint8], 2]
@external
def test() -> uint8:
    return self.foo[0][0]
```
### How can it be fixed?

Similarly to its behaviour for static arrays of `HashMap` or `Struct` with a field of type `HashMap`, the compiler should probably output an exception when defining a variable of type Dynamic array of `HashMap`  


Discussion:

Resolving patch:
diff --git a/vyper/semantics/types/base.py b/vyper/semantics/types/base.py
--- a/vyper/semantics/types/base.py
+++ b/vyper/semantics/types/base.py
@@ -12,6 +12,7 @@
 )
 from vyper.semantics.analysis.levenshtein_utils import get_levenshtein_error_suggestions
 from vyper.semantics.namespace import validate_identifier
+from vyper.utils import cached_property
 
 
 # Some fake type with an overridden `compare_type` which accepts any RHS
@@ -77,6 +78,10 @@ def __eq__(self, other):
             type(self) == type(other) and self._get_equality_attrs() == other._get_equality_attrs()
         )
 
+    @cached_property
+    def _as_darray(self):
+        return self._as_array
+
     @property
     def getter_signature(self):
         return (), self
diff --git a/vyper/semantics/types/bytestrings.py b/vyper/semantics/types/bytestrings.py
--- a/vyper/semantics/types/bytestrings.py
+++ b/vyper/semantics/types/bytestrings.py
@@ -26,6 +26,9 @@ class _BytestringT(VyperType):
         is applied to a literal definition.
     """
 
+    # this is a carveout because currently we allow dynamic arrays of
+    # bytestrings, but not static arrays of bytestrings
+    _as_darray = True
     _equality_attrs = ("_length", "_min_length")
 
     # keep LGTM linter happy:
diff --git a/vyper/semantics/types/subscriptable.py b/vyper/semantics/types/subscriptable.py
--- a/vyper/semantics/types/subscriptable.py
+++ b/vyper/semantics/types/subscriptable.py
@@ -262,6 +262,8 @@ def from_annotation(cls, node: vy_ast.Subscript) -> "DArrayT":
             )
 
         value_type = type_from_annotation(node.slice.value.elements[0])
+        if not value_type._as_darray:
+            raise StructureException(f"Arrays of {value_type} are not allowed", node)
 
         max_length = node.slice.value.elements[1].value
         return cls(value_type, max_length)


Test patch:
diff --git a/tests/parser/syntax/test_dynamic_array.py b/tests/parser/syntax/test_dynamic_array.py
new file mode 100644
--- /dev/null
+++ b/tests/parser/syntax/test_dynamic_array.py
@@ -0,0 +1,23 @@
+import pytest
+
+from vyper.exceptions import StructureException
+
+fail_list = [
+    (
+        """
+foo: DynArray[HashMap[uint8, uint8], 2]
+    """,
+        StructureException,
+    ),
+    (
+        """
+foo: public(DynArray[HashMap[uint8, uint8], 2])
+    """,
+        StructureException,
+    ),
+]
+
+
+@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)