Repository: vyperlang/vyper

Issue:
Getters for variables that are both `immutable` and `public` are not interface-conforming
### Version Information

* vyper Version (output of `vyper --version`): 0.3.7
* OS: osx
* Python Version (output of `python --version`): Python 3.8.2

### What's your issue about?

I'm working on an updated pass over https://github.com/ArtBlocks/artblocks-contracts/pull/193 now that https://github.com/vyperlang/vyper/pull/3024 has release-landed and running into an issue where if a variable is `public` it has a getter generated that is interface-conforming, but not if it is `public` _and_ `immutable` or `constant`.

Not sure if this is WAI, but it seems potentially off?

---

Notably, this compiles with compilation error:

```
genArt721CoreAddress: public(immutable(address))
```

```
Error: Command failed: /Users/jakerockland/Library/Caches/hardhat-nodejs/compilers-v2/vyper/darwin/0.3.7 -f combined_json /Users/jakerockland/Code/artblocks-contracts/contracts/minter-suite/minter_set_price_v1.vy
vyper.exceptions.InterfaceViolation: Contract does not implement all interface functions or events: genArt721CoreAddress
  contract "contracts/minter-suite/minter_set_price_v1.vy:13", line 13:0 
       12
  ---> 13 implements: IFilteredMinterV0
  --------^
       14
```

---

But this compiles without issue (is interface conforming):

```
genArt721CoreAddress: public(address)
```


Discussion:

Resolving patch:
diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py
--- a/vyper/semantics/analysis/module.py
+++ b/vyper/semantics/analysis/module.py
@@ -208,6 +208,23 @@ def visit_VariableDecl(self, node):
         node.target._metadata["varinfo"] = var_info  # TODO maybe put this in the global namespace
         node._metadata["type"] = type_
 
+        def _finalize():
+            # add the variable name to `self` namespace if the variable is either
+            # 1. a public constant or immutable; or
+            # 2. a storage variable, whether private or public
+            if (node.is_constant or node.is_immutable) and not node.is_public:
+                return
+
+            try:
+                self.namespace["self"].typ.add_member(name, var_info)
+                node.target._metadata["type"] = type_
+            except NamespaceCollision:
+                raise NamespaceCollision(
+                    f"Value '{name}' has already been declared", node
+                ) from None
+            except VyperException as exc:
+                raise exc.with_annotation(node) from None
+
         if node.is_constant:
             if not node.value:
                 raise VariableDeclarationException("Constant must be declared with a value", node)
@@ -219,7 +236,8 @@ def visit_VariableDecl(self, node):
                 self.namespace[name] = var_info
             except VyperException as exc:
                 raise exc.with_annotation(node) from None
-            return
+
+            return _finalize()
 
         if node.value:
             var_type = "Immutable" if node.is_immutable else "Storage"
@@ -237,19 +255,15 @@ def visit_VariableDecl(self, node):
                 self.namespace[name] = var_info
             except VyperException as exc:
                 raise exc.with_annotation(node) from None
-            return
+
+            return _finalize()
 
         try:
             self.namespace.validate_assignment(name)
         except NamespaceCollision as exc:
             raise exc.with_annotation(node) from None
-        try:
-            self.namespace["self"].typ.add_member(name, var_info)
-            node.target._metadata["type"] = type_
-        except NamespaceCollision:
-            raise NamespaceCollision(f"Value '{name}' has already been declared", node) from None
-        except VyperException as exc:
-            raise exc.with_annotation(node) from None
+
+        return _finalize()
 
     def visit_EnumDef(self, node):
         obj = EnumT.from_EnumDef(node)


Test patch:
diff --git a/tests/parser/syntax/test_interfaces.py b/tests/parser/syntax/test_interfaces.py
--- a/tests/parser/syntax/test_interfaces.py
+++ b/tests/parser/syntax/test_interfaces.py
@@ -210,6 +210,26 @@ def kick(): payable
 
 kickers: HashMap[address, MyInterface]
     """,
+    """
+interface ITestInterface:
+    def foo() -> uint256: view
+
+implements: ITestInterface
+
+foo: public(constant(uint256)) = 1
+    """,
+    """
+interface ITestInterface:
+    def foo() -> uint256: view
+
+implements: ITestInterface
+
+foo: public(immutable(uint256))
+
+@external
+def __init__(x: uint256):
+    foo = x
+    """,
 ]