Repository: vyperlang/vyper

Issue:
HashMap index checks when the subscript is folded
per @Leminkay

### Version Information

vyper Version (output of vyper --version): 6f09e2966b8096d8d36a85ad8745d56691dd25c4

### Issue description and/or POC
```vyper
m: HashMap[uint256, uint256]

@external
def foo():
   self.m[0-1] = 2
```
Successfully compiles

introduced in https://github.com/vyperlang/vyper/pull/3669, _related_ bug was fixed in https://github.com/vyperlang/vyper/pull/3924 (but this bug was not fixed)

Discussion:
Leminkay: LGTM!
charles-cooper: @DanielSchiavini update the commit message please with a description of how the change was arrived at, and i'll merge
codecov-commenter: ## [Codecov](https://app.codecov.io/gh/vyperlang/vyper/pull/4007?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang) Report
Attention: Patch coverage is `0%` with `4 lines` in your changes are missing coverage. Please review.
> Project coverage is 47.96%. Comparing base [(`fb55f4c`)](https://app.codecov.io/gh/vyperlang/vyper/commit/fb55f4c79654074e1abe9d202539366684c77b00?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang) to head [(`93d1bb7`)](https://app.codecov.io/gh/vyperlang/vyper/pull/4007?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang).
> Report is 1 commits behind head on master.

| [Files](https://app.codecov.io/gh/vyperlang/vyper/pull/4007?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang) | Patch % | Lines |
|---|---|---|
| [vyper/semantics/analysis/local.py](https://app.codecov.io/gh/vyperlang/vyper/pull/4007?src=pr&el=tree&filepath=vyper%2Fsemantics%2Fanalysis%2Flocal.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang#diff-dnlwZXIvc2VtYW50aWNzL2FuYWx5c2lzL2xvY2FsLnB5) | 0.00% | [4 Missing :warning: ](https://app.codecov.io/gh/vyperlang/vyper/pull/4007?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang) |

:exclamation: Your organization needs to install the [Codecov GitHub app](https://github.com/apps/codecov/installations/select_target) to enable full functionality.

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff             @@
##           master    #4007       +/-   ##
===========================================
- Coverage   90.92%   47.96%   -42.96%     
===========================================
  Files         105      105               
  Lines       15246    15247        +1     
  Branches     3357     3358        +1     
===========================================
- Hits        13862     7313     -6549     
- Misses        947     7326     +6379     
- Partials      437      608      +171     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/vyperlang/vyper/pull/4007?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vyperlang).


Patch:
diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py
index 5b20ef77..26c6a4ef 100644
--- a/vyper/semantics/analysis/local.py
+++ b/vyper/semantics/analysis/local.py
@@ -914,14 +914,14 @@ class ExprVisitor(VyperNodeVisitorBase):
         else:
             base_type = get_exact_type_from_node(node.value)
 
-        # get the correct type for the index, it might
-        # not be exactly base_type.key_type
-        # note: index_type is validated in types_from_Subscript
-        index_types = get_possible_types_from_node(node.slice)
-        index_type = index_types.pop()
+        if isinstance(base_type, HashMapT):
+            index_type = base_type.key_type
+        else:
+            # Arrays allow most int types as index: Take the least specific
+            index_type = get_possible_types_from_node(node.slice).pop()
 
-        self.visit(node.slice, index_type)
         self.visit(node.value, base_type)
+        self.visit(node.slice, index_type)
 
     def visit_Tuple(self, node: vy_ast.Tuple, typ: VyperType) -> None:
         if isinstance(typ, TYPE_T):


Test patch:
diff --git a/tests/functional/codegen/storage_variables/test_getters.py b/tests/functional/codegen/storage_variables/test_getters.py
index 5169bd30..e581d712 100644
--- a/tests/functional/codegen/storage_variables/test_getters.py
+++ b/tests/functional/codegen/storage_variables/test_getters.py
@@ -1,3 +1,9 @@
+import pytest
+
+from vyper import compile_code
+from vyper.exceptions import OverflowException, TypeMismatch
+
+
 def test_state_accessor(get_contract):
     state_accessor = """
 y: HashMap[int128, int128]
@@ -98,3 +104,32 @@ def __init__():
         if item["type"] == "constructor":
             continue
         assert item["stateMutability"] == "view"
+
+
+@pytest.mark.parametrize(
+    "typ,index,expected_error",
+    [
+        ("uint256", "-1", TypeMismatch),
+        ("uint256", "0-1", TypeMismatch),
+        ("uint256", "0-1+1", TypeMismatch),
+        ("uint256", "2**256", OverflowException),
+        ("uint256", "2**256 // 2", OverflowException),
+        ("uint256", "2 * 2**255", OverflowException),
+        ("int256", "-2**255", TypeMismatch),
+        ("int256", "-2**256", OverflowException),
+        ("int256", "2**255", TypeMismatch),
+        ("int256", "2**256 - 5", OverflowException),
+        ("int256", "2 * 2**254", TypeMismatch),
+        ("int8", "*".join(["2"] * 7), TypeMismatch),
+    ],
+)
+def test_hashmap_index_checks(typ, index, expected_error):
+    code = f"""
+m: HashMap[{typ}, uint256]
+
+@external
+def foo():
+    self.m[{index}] = 2
+    """
+    with pytest.raises(expected_error):
+        compile_code(code)