Repository: numba/numba

Issue:
Overloading unary operators throws lowering error
<!--

Thanks for opening an issue! To help the Numba team handle your information
efficiently, please first ensure that there is no other issue present that
already describes the issue you have
(search at https://github.com/numba/numba/issues?&q=is%3Aissue).

For more general "how do I do X?" type questions, please speak to us in real
time on https://gitter.im/numba/numba or post to the Numba mailing list
https://groups.google.com/a/continuum.io/forum/#!forum/numba-users.

-->

## Reporting a bug

<!--

Before submitting a bug report please ensure that you can check off these boxes:

-->

- [x] I am using the latest ~released~ master version of Numba (most recent is visible in
 the change log (https://github.com/numba/numba/blob/master/CHANGE_LOG).
- [x] I have included below a minimal working reproducer (if you are unsure how
 to write one see http://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports).

<!--

Please include details of the bug here, including, if applicable, what you
expected to happen!

-->
Overloading unary operators `invert (~)` and `pos (+)` fails.
```python
import operator
import numba
from numba.extending import (type_callable, models, register_model, overload,
    lower_builtin)


class Foo:
    def __invert__(self):
        return self


class FooType(numba.types.Type):
    def __init__(self):
        super(FooType, self).__init__(name='Foo')


foo_type = FooType()


@type_callable(Foo)
def type_foo(context):
    def typer():
        return foo_type
    return typer


@lower_builtin(Foo)
def impl_foo(context, builder, sig, args):
    return context.get_constant_null(sig.return_type)


register_model(FooType)(models.OpaqueModel)


@overload(operator.invert)
def invert_foo(f):
    if f == foo_type:
        return lambda f: f


# def myfunc(foo):
#     return ~foo


# @overload(myfunc)
# def myfunc_overload(f):
#     return lambda f: ~f


@numba.njit
def example():
    f = Foo()
    #myfunc(f)
    ~f


example()

```
```
numba.errors.LoweringError: Failed in nopython mode pipeline (step: nopython mode backend)
No definition for lowering <built-in function invert>(Foo,) -> Foo

File "ss.py", line 54:
def example():
    <source elided>
    #myfunc(f)
    ~f
    ^

[1] During: lowering "$0.4 = unary(fn=<built-in function invert>, value=f)" at ss.py (54)
```


Discussion:

Resolving patch:
diff --git a/numba/lowering.py b/numba/lowering.py
--- a/numba/lowering.py
+++ b/numba/lowering.py
@@ -904,9 +904,10 @@ def lower_expr(self, resty, expr):
         elif expr.op == 'unary':
             val = self.loadvar(expr.value.name)
             typ = self.typeof(expr.value.name)
+            func_ty = self.context.typing_context.resolve_value_type(expr.fn)
             # Get function
             signature = self.fndesc.calltypes[expr]
-            impl = self.context.get_function(expr.fn, signature)
+            impl = self.context.get_function(func_ty, signature)
             # Convert argument to match
             val = self.context.cast(self.builder, val, typ, signature.args[0])
             res = impl(self.builder, [val])
diff --git a/numba/unicode.py b/numba/unicode.py
--- a/numba/unicode.py
+++ b/numba/unicode.py
@@ -1015,6 +1015,14 @@ def wrap(a, b):
         return wrap
 
 
+@overload(operator.not_)
+def unicode_not(a):
+    if isinstance(a, types.UnicodeType):
+        def impl(a):
+            return len(a) == 0
+        return impl
+
+
 @lower_builtin('getiter', types.UnicodeType)
 def getiter_unicode(context, builder, sig, args):
     [ty] = sig.args


Test patch:
diff --git a/numba/tests/test_unicode.py b/numba/tests/test_unicode.py
--- a/numba/tests/test_unicode.py
+++ b/numba/tests/test_unicode.py
@@ -893,6 +893,16 @@ def pyfunc(x):
             self.assertEqual(pyfunc(*args), cfunc(*args),
                              msg='failed on {}'.format(args))
 
+    def test_not(self):
+        def pyfunc(x):
+            return not x
+
+        cfunc = njit(pyfunc)
+        for a in UNICODE_EXAMPLES + [""]:
+            args = [a]
+            self.assertEqual(pyfunc(*args), cfunc(*args),
+                             msg='failed on {}'.format(args))
+
 
 @unittest.skipUnless(_py34_or_later,
                      'unicode support requires Python 3.4 or later')