Repository: numba/numba

Issue:
Overload of Numpy ufuncs doesn't work
<!--

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!

-->
Overload of ufuncs doesn't work. A minimal reproducer is below:
```python
@numba.extending.overload(np.exp)
def exp_list_overload(A):
   if isinstance(A, numba.types.List):
      return lambda A: np.exp(np.array(A))

@numba.njit
def f():
    return np.exp([1,2,3])

f()
```
```
TypingError: Failed in nopython mode pipeline (step: nopython frontend)
Invalid use of Function(<ufunc 'exp'>) with argument(s) of type(s): (list(int64))
 * parameterized
In definition 0:
    TypingError: can't resolve ufunc exp for types (list(int64),)
    raised from /home/ehsan/dev/numba/numba/typing/npydecl.py:109
In definition 1:
    TypingError: can't resolve ufunc exp for types (list(int64),)
    raised from /home/ehsan/dev/numba/numba/typing/npydecl.py:109
In definition 2:
    All templates rejected with literals.
In definition 3:
    All templates rejected without literals.
This error is usually caused by passing an argument of a type that is unsupported by the named function.
[1] During: resolving callee type: Function(<ufunc 'exp'>)
[2] During: typing of call at <ipython-input-2-e2192680c324> (7)


File "<ipython-input-2-e2192680c324>", line 7:
def f():
    return np.exp([1,2,3])
    ^
```


Discussion:
This is almost certainly a collision between the old-style and new-style registration of functions.  @sklam: thoughts?
Oh, very likely.  
The problem is caused by https://github.com/numba/numba/blob/309d6157a07b370b129688c68548c7f64df67046/numba/extending.py#L94-L95 disabling the `infer_global` for `np.ufunc` objects.  I need to review why that branch is needed.

Resolving patch:
diff --git a/numba/extending.py b/numba/extending.py
--- a/numba/extending.py
+++ b/numba/extending.py
@@ -39,7 +39,7 @@ def generic(self):
         class_dict = dict(key=func, generic=generic)
         template = type(name, bases, class_dict)
         infer(template)
-        if hasattr(func, '__module__'):
+        if callable(func):
             infer_global(func, types.Function(template))
 
     return decorate
@@ -91,7 +91,7 @@ def len_impl(seq):
     def decorate(overload_func):
         template = make_overload_template(func, overload_func, opts, strict)
         infer(template)
-        if hasattr(func, '__module__'):
+        if callable(func):
             infer_global(func, types.Function(template))
         return overload_func
 


Test patch:
diff --git a/numba/tests/test_extending.py b/numba/tests/test_extending.py
--- a/numba/tests/test_extending.py
+++ b/numba/tests/test_extending.py
@@ -411,6 +411,18 @@ def mk_func_test_impl():
     mk_func_input(lambda a: a)
 
 
+# -----------------------------------------------------------------------
+
+
+@overload(np.exp)
+def overload_np_exp(obj):
+    if isinstance(obj, MyDummyType):
+        def imp(obj):
+            # Returns a constant if a MyDummyType is seen
+            return 0xdeadbeef
+        return imp
+
+
 class TestLowLevelExtending(TestCase):
     """
     Test the low-level two-tier extension API.
@@ -963,6 +975,16 @@ def bar(A):
         bar(A)
         self.assertEqual(bar(A), 0xcafe)
 
+    def test_overload_ufunc(self):
+        # Issue #4133.
+        # Use an extended type (MyDummyType) to use with a customized
+        # ufunc (np.exp).
+        @njit
+        def test():
+            return np.exp(mydummy)
+
+        self.assertEqual(test(), 0xdeadbeef)
+
 
 def _assert_cache_stats(cfunc, expect_hit, expect_misses):
     hit = cfunc._cache_hits[cfunc.signatures[0]]