Repository: vyperlang/vyper

Issue:
Unclear error message when annotating `implements` with something else than a `Name`
### Version Information

* vyper Version (output of `vyper --version`): 0.3.8+commit.7ff8f307
* OS: OSX
* Python Version (output of `python --version`): 3.8.0

### What's your issue about?

When improperly using `implements` and passing it something else than a `Name` as an annotation, the compiler outputs an unclear error as the `UnexpectedNodeType` it tries to raise is constructed with 2 arguments instead of 1.
https://github.com/vyperlang/vyper/blob/0a64906a02018aa3e967c311d9dd89eba273bb11/vyper/ast/nodes.py#L1404-L1405

Additionally, `UnexpectedNodeType` is an exception used to notify the user that what happened is an unhandled internal compiler error while it does not seem to be the case here.

The following code outputs the given error:

```Vyper
implements: self.x
```

```Bash
Traceback (most recent call last):
  File "/Users/trocher/Documents/thesis/vyper/venv/bin/vyper", line 11, in <module>
    load_entry_point('vyper==0.3.8', 'console_scripts', 'vyper')()
  File "/Users/trocher/Documents/thesis/vyper/vyper/cli/vyper_compile.py", line 57, in _parse_cli_args
    return _parse_args(sys.argv[1:])
  File "/Users/trocher/Documents/thesis/vyper/vyper/cli/vyper_compile.py", line 154, in _parse_args
    compiled = compile_files(
  File "/Users/trocher/Documents/thesis/vyper/vyper/cli/vyper_compile.py", line 298, in compile_files
    interface_codes=get_interface_codes(root_path, contract_sources),
  File "/Users/trocher/Documents/thesis/vyper/vyper/cli/vyper_compile.py", line 200, in get_interface_codes
    interface_codes = extract_file_interface_imports(code)
  File "/Users/trocher/Documents/thesis/vyper/vyper/cli/utils.py", line 30, in extract_file_interface_imports
    ast_tree = vy_ast.parse_to_ast(code)
  File "/Users/trocher/Documents/thesis/vyper/vyper/ast/utils.py", line 54, in parse_to_ast
    return vy_ast.get_node(py_ast)  # type: ignore
  File "/Users/trocher/Documents/thesis/vyper/vyper/ast/nodes.py", line 92, in get_node
    return vy_class(parent=parent, **ast_struct)
  File "/Users/trocher/Documents/thesis/vyper/vyper/ast/nodes.py", line 274, in __init__
    value = [_to_node(i, self) for i in value]
  File "/Users/trocher/Documents/thesis/vyper/vyper/ast/nodes.py", line 274, in <listcomp>
    value = [_to_node(i, self) for i in value]
  File "/Users/trocher/Documents/thesis/vyper/vyper/ast/nodes.py", line 141, in _to_node
    return get_node(obj, parent)
  File "/Users/trocher/Documents/thesis/vyper/vyper/ast/nodes.py", line 92, in get_node
    return vy_class(parent=parent, **ast_struct)
  File "/Users/trocher/Documents/thesis/vyper/vyper/ast/nodes.py", line 1404, in __init__
    raise UnexpectedNodeType("not an identifier", self.annotation)
TypeError: __init__() takes from 1 to 2 positional arguments but 3 were given
```
Annotation of `implements` is not properly type-checked
### Version Information

* vyper Version (output of `vyper --version`): 0.3.8+commit.7ff8f307
* OS: OSX
* Python Version (output of `python --version`): 3.8.0

### What's your issue about?

At the moment, `visit_ImplementsDecl` relies on the fact that the object mapped to `node.annotation.id` in the namespace defines a method `validate_implements` to ensure that the given name corresponds to an interface.
https://github.com/vyperlang/vyper/blob/0a64906a02018aa3e967c311d9dd89eba273bb11/vyper/semantics/analysis/module.py#L157-L161

It would probably be better to use `type_from_annotation`:

```Python
  def visit_ImplementsDecl(self, node):
      interface_name = node.annotation

      other_iface = type_from_annotation(interface_name)

      # Add a check to ensure that: isinstance(other_iface,InterfaceT)

      other_iface.validate_implements(node)
```

This would also have the advantage to give better error messages for the 2 following cases:
- When a constant is given (currently the following is given `AttributeError: 'Int' object has no attribute 'id'`
- When a type or more generally an identifier which is not an interface is given (currently for example: `AttributeError: 'StructT' object has no attribute 'validate_implements'`)


Discussion:

Resolving patch:
diff --git a/vyper/ast/nodes.py b/vyper/ast/nodes.py
--- a/vyper/ast/nodes.py
+++ b/vyper/ast/nodes.py
@@ -12,9 +12,9 @@
     InvalidLiteral,
     InvalidOperation,
     OverflowException,
+    StructureException,
     SyntaxException,
     TypeMismatch,
-    UnexpectedNodeType,
     UnfoldableNode,
     ZeroDivisionException,
 )
@@ -1402,7 +1402,7 @@ def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
 
         if not isinstance(self.annotation, Name):
-            raise UnexpectedNodeType("not an identifier", self.annotation)
+            raise StructureException("not an identifier", self.annotation)
 
 
 class If(Stmt):
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
@@ -155,10 +155,11 @@ def __init__(
             self_members[fn_name].recursive_calls = function_set
 
     def visit_ImplementsDecl(self, node):
-        interface_name = node.annotation.id
+        type_ = type_from_annotation(node.annotation)
+        if not isinstance(type_, InterfaceT):
+            raise StructureException("Invalid interface name", node.annotation)
 
-        other_iface = self.namespace[interface_name]
-        other_iface.validate_implements(node)
+        type_.validate_implements(node)
 
     def visit_VariableDecl(self, node):
         name = node.get("target.id")


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
@@ -85,6 +85,27 @@ def foo(): nonpayable
     """,
         StructureException,
     ),
+    (
+        """
+implements: self.x
+    """,
+        StructureException,
+    ),
+    (
+        """
+implements: 123
+    """,
+        StructureException,
+    ),
+    (
+        """
+struct Foo:
+    a: uint256
+
+implements: Foo
+    """,
+        StructureException,
+    ),
 ]