Repository: vyperlang/vyper

Issue:
can't call pure internal function from pure function
### Version Information

* vyper Version (output of `vyper --version`): 42a372e95cb50ead4c9906cd0c6c50d5de881f8b
* Python Version (output of `python --version`): v3.8.10

### What's your issue about?
repro:
```vyper
@pure
@internal
def foo():
    pass

@pure
@internal
def bar():
    self.foo()
```




Discussion:

Resolving patch:
diff --git a/vyper/semantics/validation/local.py b/vyper/semantics/validation/local.py
--- a/vyper/semantics/validation/local.py
+++ b/vyper/semantics/validation/local.py
@@ -183,6 +183,15 @@ def __init__(
         self.expr_visitor = _LocalExpressionVisitor()
         namespace.update(self.func.arguments)
 
+        for node in fn_node.body:
+            self.visit(node)
+        if self.func.return_type:
+            if not check_for_terminus(fn_node.body):
+                raise FunctionDeclarationException(
+                    f"Missing or unmatched return statements in function '{fn_node.name}'",
+                    fn_node,
+                )
+
         if self.func.mutability == StateMutability.PURE:
             node_list = fn_node.get_descendants(
                 vy_ast.Attribute,
@@ -192,7 +201,11 @@ def __init__(
                     )
                 },
             )
-            if node_list:
+            for node in node_list:
+                t = node._metadata.get("type")
+                if isinstance(t, ContractFunction) and t.mutability == StateMutability.PURE:
+                    # allowed
+                    continue
                 raise StateAccessViolation(
                     "not allowed to query contract or environment variables in pure functions",
                     node_list[0],
@@ -206,15 +219,6 @@ def __init__(
                     "msg.value is not allowed in non-payable functions", node_list[0]
                 )
 
-        for node in fn_node.body:
-            self.visit(node)
-        if self.func.return_type:
-            if not check_for_terminus(fn_node.body):
-                raise FunctionDeclarationException(
-                    f"Missing or unmatched return statements in function '{fn_node.name}'",
-                    fn_node,
-                )
-
     def visit(self, node):
         super().visit(node)
         self.annotation_visitor.visit(node)
@@ -475,9 +479,12 @@ def visit_Expr(self, node):
                     node,
                 )
 
-            if self.func.mutability == StateMutability.PURE:
+            if (
+                self.func.mutability == StateMutability.PURE
+                and fn_type.mutability != StateMutability.PURE
+            ):
                 raise StateAccessViolation(
-                    f"Cannot call any function from a {self.func.mutability.value} function", node
+                    "Cannot call non-pure function from a pure function", node
                 )
 
         if isinstance(fn_type, MemberFunctionDefinition) and fn_type.is_modifying:


Test patch:
diff --git a/tests/parser/features/decorators/test_pure.py b/tests/parser/features/decorators/test_pure.py
--- a/tests/parser/features/decorators/test_pure.py
+++ b/tests/parser/features/decorators/test_pure.py
@@ -21,7 +21,7 @@ def test_pure_call(get_contract_with_gas_estimation_for_constants):
 def _foo() -> int128:
     return 5
 
-@view
+@pure
 @external
 def foo() -> int128:
     return self._foo()
@@ -87,7 +87,7 @@ def test_invalid_call(get_contract, assert_compile_failed):
     assert_compile_failed(
         lambda: get_contract(
             """
-@pure
+@view
 @internal
 def _foo() -> uint256:
     return 5
@@ -95,7 +95,7 @@ def _foo() -> uint256:
 @pure
 @external
 def foo() -> uint256:
-    return self._foo()  # Fails because of self.
+    return self._foo()  # Fails because of calling non-pure fn
     """
         ),
         StateAccessViolation,