Repository: vyperlang/vyper

Issue:
Calling convert() without assigning the result to a variable causes an error
* vyper Version: Master
* pyethereum Version: x.x.x (Not sure how to find this. I built the environment using make in a clean ubuntu install so it's probably the latest. The version of pyethereum shouldn't matter for this issue anyway.)
* OS: Ubuntu
* Python Version (python --version): 3.6.3
* Environment (output of `pip freeze`):

### What's your issue about?

Code:

```python
@public
def foo() -> uint256:
    convert(2, 'uint256')
    return convert(2, 'uint256')
```

On running `vyper filename.py` throws the error: `AttributeError: 'Name' object has no attribute 'value'`

In `parser.py:parse_func`
`code.body` is: `[<_ast.Expr object at 0x7f4ff8fce160>, <_ast.Return object at 0x7f4ff8fce358>]`

Because of the Expression, execution reaches `stmt.py:Stmt.call` and an error is thrown in the line `elif isinstance(self.stmt.func.value, ast.Attribute) and self.stmt.func.value.attr in self.context.sigs:` because `self.stmt.func` is an object of type `Name` and it does not have an attribute `value`.

On assigning the return value of `convert()` to a variable, `code.body` in `parser.py:parse_func` becomes `[<_ast.AnnAssign object at 0x7ff077c7c160>, <_ast.Return object at 0x7ff077c7c3c8>]` and this error does not occur.

---

The issue can also be replicated by calling `max()`, `min()` and other such functions and not assigning their return value to a variable.

---

This issue does not occur if the return value of a `self.method()` is not assigned to a variable.

### How can it be fixed?

- Should code be allowed to call a method but ignore the return value?
- If the above behaviour should not be allowed, the error message should be better.

#### Cute Animal Picture

```
                           ____
  ________________________/ O  \___/
 <_/_\_/_\_/_\_/_\_/_\_/_______/   \
```


Discussion:
Better error message is needed. Thanks!

Resolving patch:
diff --git a/vyper/parser/stmt.py b/vyper/parser/stmt.py
--- a/vyper/parser/stmt.py
+++ b/vyper/parser/stmt.py
@@ -10,6 +10,7 @@
 )
 from vyper.functions import (
     stmt_dispatch_table,
+    dispatch_table
 )
 from vyper.parser.parser_utils import LLLnode
 from vyper.parser.parser_utils import (
@@ -167,8 +168,13 @@ def call(self):
             pack_logging_topics,
             external_contract_call,
         )
-        if isinstance(self.stmt.func, ast.Name) and self.stmt.func.id in stmt_dispatch_table:
+        if isinstance(self.stmt.func, ast.Name):
+            if self.stmt.func.id in stmt_dispatch_table:
                 return stmt_dispatch_table[self.stmt.func.id](self.stmt, self.context)
+            elif self.stmt.func.id in dispatch_table:
+                raise StructureException("Function {} can not be called without being used.".format(self.stmt.func.id), self.stmt)
+            else:
+                raise StructureException("Unknow function: '{}'.".format(self.stmt.func.id), self.stmt)
         elif isinstance(self.stmt.func, ast.Attribute) and isinstance(self.stmt.func.value, ast.Name) and self.stmt.func.value.id == "self":
             method_name = self.stmt.func.attr
             if method_name not in self.context.sigs['self']:


Test patch:
diff --git a/tests/parser/syntax/test_functions_call.py b/tests/parser/syntax/test_functions_call.py
new file mode 100644
--- /dev/null
+++ b/tests/parser/syntax/test_functions_call.py
@@ -0,0 +1,46 @@
+import pytest
+from pytest import raises
+
+from vyper import compiler
+from vyper.exceptions import StructureException
+
+fail_list = [
+    """
+@public
+def foo() -> uint256:
+    doesnotexist(2, 'uint256')
+    return convert(2, 'uint256')
+    """,
+    """
+@public
+def foo() -> uint256:
+    convert(2, 'uint256')
+    return convert(2, 'uint256')
+
+    """
+]
+
+
+@pytest.mark.parametrize('bad_code', fail_list)
+def test_raw_call_fail(bad_code):
+
+    if isinstance(bad_code, tuple):
+        with raises(bad_code[1]):
+            compiler.compile(bad_code[0])
+    else:
+        with raises(StructureException):
+            compiler.compile(bad_code)
+
+
+valid_list = [
+    """
+@public
+def foo() -> uint256:
+    return convert(2, 'uint256')
+    """
+]
+
+
+@pytest.mark.parametrize('good_code', valid_list)
+def test_raw_call_success(good_code):
+    assert compiler.compile(good_code) is not None
diff --git a/tests/parser/syntax/test_invalids.py b/tests/parser/syntax/test_invalids.py
--- a/tests/parser/syntax/test_invalids.py
+++ b/tests/parser/syntax/test_invalids.py
@@ -2,7 +2,7 @@
 from pytest import raises
 
 from vyper import compiler
-from vyper.exceptions import TypeMismatchException
+from vyper.exceptions import TypeMismatchException, StructureException
 
 
 # These functions register test cases
@@ -245,7 +245,7 @@ def foo():
 @public
 def foo():
     suicide(msg.sender)
-    """, AttributeError)
+    """, StructureException)
 
 
 @pytest.mark.parametrize('bad_code,exception_type', fail_list)