Repository: vyperlang/vyper

Issue:
bug: references to public constants and immutables with self are missed by the typechecker
### Version Information

* vyper Version (output of `vyper --version`): https://github.com/vyperlang/vyper/commit/9e3b9a2b8ae55aa83b5450080f750be15f819de7
* OS: linux
* Python Version (output of `python --version`): 3.10.4

### What's your issue about?

As `visit_VariableDecl` adds public constant and immutables variables to self's namespace, `types_from_Attribute` successfully typecheck references to constant and immutables using self. The compiler later fails during the codegen.

Compiling the following contract will fail with `KeyError: 'a'`.
```
a:public(constant(uint256)) = 1

@external
def foo():
    b: uint256 = self.a
```

h/t @trocher 

### How can it be fixed?

Fill this in if you know how to fix it.



Discussion:
Public immutable throws this exception `AttributeError: 'CodeOffset' object has no attribute 'position'` as of https://github.com/vyperlang/vyper/commit/6bd81dea55d0d0ef71b192bf30331a48a409f1d4.

Resolving patch:
diff --git a/vyper/semantics/analysis/utils.py b/vyper/semantics/analysis/utils.py
--- a/vyper/semantics/analysis/utils.py
+++ b/vyper/semantics/analysis/utils.py
@@ -180,24 +180,30 @@ def _find_fn(self, node):
         raise StructureException("Cannot determine type of this object", node)
 
     def types_from_Attribute(self, node):
+        is_self_reference = node.get("value.id") == "self"
         # variable attribute, e.g. `foo.bar`
         t = self.get_exact_type_from_node(node.value, include_type_exprs=True)
         name = node.attr
+
+        def _raise_invalid_reference(name, node):
+            raise InvalidReference(
+                f"'{name}' is not a storage variable, it should not be prepended with self", node
+            )
+
         try:
             s = t.get_member(name, node)
             if isinstance(s, VyperType):
                 # ex. foo.bar(). bar() is a ContractFunctionT
                 return [s]
+            if is_self_reference and (s.is_constant or s.is_immutable):
+                _raise_invalid_reference(name, node)
             # general case. s is a VarInfo, e.g. self.foo
             return [s.typ]
         except UnknownAttribute:
-            if node.get("value.id") != "self":
+            if not is_self_reference:
                 raise
             if name in self.namespace:
-                raise InvalidReference(
-                    f"'{name}' is not a storage variable, it should not be prepended with self",
-                    node,
-                ) from None
+                _raise_invalid_reference(name, node)
 
             suggestions_str = get_levenshtein_error_suggestions(name, t.members, 0.4)
             raise UndeclaredDefinition(


Test patch:
diff --git a/tests/parser/exceptions/test_invalid_reference.py b/tests/parser/exceptions/test_invalid_reference.py
--- a/tests/parser/exceptions/test_invalid_reference.py
+++ b/tests/parser/exceptions/test_invalid_reference.py
@@ -37,6 +37,24 @@ def foo():
 def foo():
     int128 = 5
     """,
+    """
+a: public(constant(uint256)) = 1
+
+@external
+def foo():
+    b: uint256 = self.a
+    """,
+    """
+a: public(immutable(uint256))
+
+@external
+def __init__():
+    a = 123
+
+@external
+def foo():
+    b: uint256 = self.a
+    """,
 ]