Repository: vyperlang/vyper

Issue:
`TypeCheckFailure` when using `not` with integers and decimals
### Version Information

* vyper Version (output of `vyper --version`): 0.3.8+commit.a16af3d7
* OS: OSX
* Python Version (output of `python --version`): 3.8.0

### What's your issue about?

When trying to use the boolean operator `not` in an integer or decimal expression, instead of raising an exception during the semantic analysis, the compiler goes to codegen before crashing with a `TypeCheckFailure`.

```Vyper
@external
def test():
    b:uint256 = not 3
```
```Bash
Error compiling: tests/customs/code.vy
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 294, in compile_files
    compiler_data = vyper.compile_codes(
  File "/Users/trocher/Documents/thesis/vyper/vyper/evm/opcodes.py", line 226, in _wrapper
    return fn(*args, **kwargs)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/__init__.py", line 141, in compile_codes
    exc_handler(contract_name, exc)
  File "/Users/trocher/Documents/thesis/vyper/vyper/cli/vyper_compile.py", line 189, in exc_handler
    raise exception
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/__init__.py", line 138, in compile_codes
    out[contract_name][output_format] = OUTPUT_FORMATS[output_format](compiler_data)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/output.py", line 248, in build_bytecode_output
    return f"0x{compiler_data.bytecode.hex()}"
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/functools.py", line 966, in __get__
    val = self.func(instance)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/phases.py", line 150, in bytecode
    self.assembly, is_runtime=False, no_bytecode_metadata=self.no_bytecode_metadata
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/functools.py", line 966, in __get__
    val = self.func(instance)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/phases.py", line 141, in assembly
    return generate_assembly(self.ir_nodes, self.no_optimize)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/phases.py", line 126, in ir_nodes
    ir, ir_runtime, sigs = self._ir_output
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/functools.py", line 966, in __get__
    val = self.func(instance)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/phases.py", line 122, in _ir_output
    return generate_ir_nodes(self.global_ctx, self.no_optimize)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/phases.py", line 258, in generate_ir_nodes
    ir_nodes, ir_runtime, function_sigs = module.generate_ir_for_module(global_ctx)
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/module.py", line 165, in generate_ir_for_module
    runtime, internal_functions = _runtime_ir(runtime_functions, all_sigs, global_ctx)
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/module.py", line 103, in _runtime_ir
    func_ir = generate_ir_for_function(func_ast, all_sigs, global_ctx, skip_nonpayable_check)
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/function_definitions/common.py", line 62, in generate_ir_for_function
    o = generate_ir_for_external_function(code, sig, context, skip_nonpayable_check)
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/function_definitions/external_function.py", line 182, in generate_ir_for_external_function
    body += [parse_body(code.body, context, ensure_terminated=True)]
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/stmt.py", line 410, in parse_body
    ir = parse_stmt(stmt, context)
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/stmt.py", line 384, in parse_stmt
    return Stmt(stmt, context).ir_node
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/stmt.py", line 40, in __init__
    self.ir_node = fn()
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/stmt.py", line 67, in parse_AnnAssign
    rhs = Expr(self.stmt.value, self.context).ir_node
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/expr.py", line 79, in __init__
    raise TypeCheckFailure(f"{type(node).__name__} node did not produce IR. {self.expr}")
vyper.exceptions.TypeCheckFailure: UnaryOp node did not produce IR. vyper.ast.nodes.UnaryOp:
     4 def test():
---> 5     b:uint256 = not 3
-----------------------^

This is an unhandled internal compiler error. Please create an issue on Github to notify the developers.
https://github.com/vyperlang/vyper/issues/new?template=bug.md
```
### How can it be fixed?

My guess here would be to update the `_invalid_ops` functions of `IntegerT` and `DecimalT` to includes `vy_ast.Not`.


Discussion:

Resolving patch:
diff --git a/vyper/semantics/types/primitives.py b/vyper/semantics/types/primitives.py
--- a/vyper/semantics/types/primitives.py
+++ b/vyper/semantics/types/primitives.py
@@ -219,9 +219,10 @@ def ast_bounds(self) -> Tuple[int, int]:
 
     @cached_property
     def _invalid_ops(self):
+        invalid_ops = (vy_ast.Not,)
         if not self.is_signed:
-            return (vy_ast.USub,)
-        return ()
+            return invalid_ops + (vy_ast.USub,)
+        return invalid_ops
 
     @classmethod
     # TODO maybe cache these three classmethods
@@ -265,7 +266,7 @@ class DecimalT(NumericT):
     _decimal_places = 10  # TODO generalize
     _id = "decimal"
     _is_signed = True
-    _invalid_ops = (vy_ast.Pow, vy_ast.BitAnd, vy_ast.BitOr, vy_ast.BitXor)
+    _invalid_ops = (vy_ast.Pow, vy_ast.BitAnd, vy_ast.BitOr, vy_ast.BitXor, vy_ast.Not)
     _valid_literal = (vy_ast.Decimal,)
 
     _equality_attrs = ("_bits", "_decimal_places")


Test patch:
diff --git a/tests/parser/syntax/test_bool.py b/tests/parser/syntax/test_bool.py
--- a/tests/parser/syntax/test_bool.py
+++ b/tests/parser/syntax/test_bool.py
@@ -63,24 +63,6 @@ def foo(a: address) -> bool:
     """,
         InvalidOperation,
     ),
-    """
-@external
-def foo() -> bool:
-    b: int128 = 0
-    return not b
-    """,
-    """
-@external
-def foo() -> bool:
-    b: uint256 = 0
-    return not b
-    """,
-    """
-@external
-def foo() -> bool:
-    b: uint256 = 0
-    return not b
-    """,
     (
         """
 @external
diff --git a/tests/parser/types/numbers/test_decimals.py b/tests/parser/types/numbers/test_decimals.py
--- a/tests/parser/types/numbers/test_decimals.py
+++ b/tests/parser/types/numbers/test_decimals.py
@@ -33,6 +33,16 @@ def foo(x: decimal, y: decimal) -> decimal:
     assert_compile_failed(lambda: get_contract(code), InvalidOperation)
 
 
+@pytest.mark.parametrize("op", ["not"])
+def test_invalid_unary_ops(get_contract, assert_compile_failed, op):
+    code = f"""
+@external
+def foo(x: decimal) -> decimal:
+    return {op} x
+    """
+    assert_compile_failed(lambda: get_contract(code), InvalidOperation)
+
+
 def quantize(x: Decimal) -> Decimal:
     return x.quantize(DECIMAL_EPSILON, rounding=ROUND_DOWN)
 
diff --git a/tests/parser/types/numbers/test_signed_ints.py b/tests/parser/types/numbers/test_signed_ints.py
--- a/tests/parser/types/numbers/test_signed_ints.py
+++ b/tests/parser/types/numbers/test_signed_ints.py
@@ -4,7 +4,7 @@
 
 import pytest
 
-from vyper.exceptions import InvalidType, OverflowException, ZeroDivisionException
+from vyper.exceptions import InvalidOperation, InvalidType, OverflowException, ZeroDivisionException
 from vyper.semantics.types import IntegerT
 from vyper.utils import evm_div, evm_mod
 
@@ -389,3 +389,14 @@ def foo(a: {typ}) -> {typ}:
     assert c.foo(-2) == 2
 
     assert_tx_failed(lambda: c.foo(lo))
+
+
+@pytest.mark.parametrize("typ", types)
+@pytest.mark.parametrize("op", ["not"])
+def test_invalid_unary_ops(get_contract, assert_compile_failed, typ, op):
+    code = f"""
+@external
+def foo(a: {typ}) -> {typ}:
+    return {op} a
+    """
+    assert_compile_failed(lambda: get_contract(code), InvalidOperation)
diff --git a/tests/parser/types/numbers/test_unsigned_ints.py b/tests/parser/types/numbers/test_unsigned_ints.py
--- a/tests/parser/types/numbers/test_unsigned_ints.py
+++ b/tests/parser/types/numbers/test_unsigned_ints.py
@@ -4,7 +4,7 @@
 
 import pytest
 
-from vyper.exceptions import InvalidType, OverflowException, ZeroDivisionException
+from vyper.exceptions import InvalidOperation, InvalidType, OverflowException, ZeroDivisionException
 from vyper.semantics.types import IntegerT
 from vyper.utils import evm_div, evm_mod
 
@@ -257,3 +257,14 @@ def test() -> {typ}:
 
     for val in bad_cases:
         assert_compile_failed(lambda: get_contract(code_template.format(typ=typ, val=val)))
+
+
+@pytest.mark.parametrize("typ", types)
+@pytest.mark.parametrize("op", ["not", "-"])
+def test_invalid_unary_ops(get_contract, assert_compile_failed, typ, op):
+    code = f"""
+@external
+def foo(a: {typ}) -> {typ}:
+    return {op} a
+    """
+    assert_compile_failed(lambda: get_contract(code), InvalidOperation)