Repository: numba/numba

Issue:
np.angle() fails on arrays
```
>>> from numba import jit
>>> @jit(nopython=True)
   ...: def angle(x): return np.angle(x)
   ...: 
>>> 
>>> angle(np.complex128([1+1j]))
Traceback (most recent call last):
  File "<ipython-input-6-186e6f934ae3>", line 1, in <module>
    angle(np.complex128([1+1j]))
  File "/home/antoine/numba/numba/dispatcher.py", line 171, in _compile_for_args
    return self.compile(sig)
  File "/home/antoine/numba/numba/dispatcher.py", line 349, in compile
    flags=flags, locals=self.locals)
  File "/home/antoine/numba/numba/compiler.py", line 684, in compile_extra
    return pipeline.compile_extra(func)
  File "/home/antoine/numba/numba/compiler.py", line 372, in compile_extra
    return self.compile_bytecode(bc, func_attr=self.func_attr)
  File "/home/antoine/numba/numba/compiler.py", line 381, in compile_bytecode
    return self._compile_bytecode()
  File "/home/antoine/numba/numba/compiler.py", line 665, in _compile_bytecode
    res = pm.run(self.status)
  File "/home/antoine/numba/numba/compiler.py", line 251, in run
    raise patched_exception
  File "/home/antoine/numba/numba/compiler.py", line 243, in run
    stage()
  File "/home/antoine/numba/numba/compiler.py", line 469, in stage_nopython_frontend
    self.locals)
  File "/home/antoine/numba/numba/compiler.py", line 799, in type_inference_stage
    infer.propagate()
  File "/home/antoine/numba/numba/typeinfer.py", line 565, in propagate
    raise errors[0]
TypingError: Internal error at <numba.typeinfer.CallConstraint object at 0x7fa910005470>:
--%<-----------------------------------------------------------------
Traceback (most recent call last):
  File "/home/antoine/numba/numba/typeinfer.py", line 111, in propagate
    constraint(typeinfer)
  File "/home/antoine/numba/numba/typeinfer.py", line 284, in __call__
    self.resolve(typeinfer, typevars, fnty)
  File "/home/antoine/numba/numba/typeinfer.py", line 311, in resolve
    sig = context.resolve_function_type(fnty, pos_args, kw_args)
  File "/home/antoine/numba/numba/typing/context.py", line 123, in resolve_function_type
    return func.get_call_type(self, args, kws)
  File "/home/antoine/numba/numba/types.py", line 264, in get_call_type
    sig = temp.apply(args, kws)
  File "/home/antoine/numba/numba/typing/templates.py", line 229, in apply
    sig = typer(*args, **kws)
  File "/home/antoine/numba/numba/typing/npydecl.py", line 850, in typer
    return ref.copy(dtype=ref.underlying_float)
AttributeError: 'Array' object has no attribute 'underlying_float'
--%<-----------------------------------------------------------------

```



Discussion:
np.sinc() may suffer from the same problem, given its tests in `numba.tests.test_np_functions` are written in the same way.


Resolving patch:
diff --git a/numba/targets/arraymath.py b/numba/targets/arraymath.py
--- a/numba/targets/arraymath.py
+++ b/numba/targets/arraymath.py
@@ -438,42 +438,44 @@ def scalar_sinc_impl(val):
                                    locals=dict(c=scalar_dtype))
     return impl_ret_untracked(context, builder, sig.return_type, res)
 
-@lower_builtin(numpy.angle, types.Number)
-def scalar_angle(context, builder, sig, args):
-    def scalar_angle_impl(val):
-          return numpy.arctan2(val.imag, val.real)
-    res = context.compile_internal(builder, scalar_angle_impl,
-                                      sig, args)
-    return impl_ret_untracked(context, builder, sig.return_type, res)
 
+@lower_builtin(numpy.angle, types.Number)
 @lower_builtin(numpy.angle, types.Number, types.Boolean)
 def scalar_angle_kwarg(context, builder, sig, args):
-    def scalar_angle_impl(val, deg=False):
+    deg_mult = sig.return_type(180 / numpy.pi)
+    def scalar_angle_impl(val, deg):
         if deg:
-            scal = 180/numpy.pi
-            return numpy.arctan2(val.imag, val.real) * scal
+            return numpy.arctan2(val.imag, val.real) * deg_mult
         else:
             return numpy.arctan2(val.imag, val.real)
+
+    if len(args) == 1:
+        args = args + (cgutils.false_bit,)
+        sig = signature(sig.return_type, *(sig.args + (types.boolean,)))
     res = context.compile_internal(builder, scalar_angle_impl,
-                                      sig, args)
+                                   sig, args)
     return impl_ret_untracked(context, builder, sig.return_type, res)
 
 @lower_builtin(numpy.angle, types.Array)
 @lower_builtin(numpy.angle, types.Array, types.Boolean)
 def array_angle_kwarg(context, builder, sig, args):
     arg = sig.args[0]
-    if isinstance(arg.dtype, types.Complex):
-        retty = arg.dtype.underlying_float
-    else:
-        retty = arg.dtype
-    def array_angle_impl(arr, deg=False):
-        out = numpy.zeros_like(arr, dtype=retty)
+    ret_dtype = sig.return_type.dtype
+
+    def array_angle_impl(arr, deg):
+        out = numpy.zeros_like(arr, dtype=ret_dtype)
         for index, val in numpy.ndenumerate(arr):
             out[index] = numpy.angle(val, deg)
         return out
+
+    if len(args) == 1:
+        args = args + (cgutils.false_bit,)
+        sig = signature(sig.return_type, *(sig.args + (types.boolean,)))
+
     res = context.compile_internal(builder, array_angle_impl, sig, args)
     return impl_ret_new_ref(context, builder, sig.return_type, res)
 
+
 @lower_builtin(numpy.nonzero, types.Array)
 @lower_builtin("array.nonzero", types.Array)
 @lower_builtin(numpy.where, types.Array)
diff --git a/numba/typing/npydecl.py b/numba/typing/npydecl.py
--- a/numba/typing/npydecl.py
+++ b/numba/typing/npydecl.py
@@ -914,21 +914,32 @@ class Angle(CallableTemplate):
     Typing template for np.angle()
     """
     def generic(self):
-        def typer(ref, deg = False):
-            if isinstance(ref, types.Array):
-                return ref.copy(dtype=ref.underlying_float)
+        def typer(z, deg=False):
+            if isinstance(z, types.Array):
+                dtype = z.dtype
+            else:
+                dtype = z
+            if isinstance(dtype, types.Complex):
+                ret_dtype = dtype.underlying_float
+            elif isinstance(dtype, types.Float):
+                ret_dtype = dtype
+            else:
+                return
+            if isinstance(z, types.Array):
+                return z.copy(dtype=ret_dtype)
             else:
-                return types.float64
+                return ret_dtype
         return typer
 
+
 @infer_global(numpy.diag)
 class DiagCtor(CallableTemplate):
     """
     Typing template for np.diag()
     """
     def generic(self):
-        def typer(ref, k = 0):
-            if(isinstance(ref, types.Array)):
+        def typer(ref, k=0):
+            if isinstance(ref, types.Array):
                 if ref.ndim == 1:
                     rdim = 2
                 elif ref.ndim == 2:


Test patch:
diff --git a/numba/tests/test_np_functions.py b/numba/tests/test_np_functions.py
--- a/numba/tests/test_np_functions.py
+++ b/numba/tests/test_np_functions.py
@@ -9,19 +9,23 @@
 
 from numba import unittest_support as unittest
 from numba.compiler import compile_isolated, Flags, utils
-from numba import types
+from numba import typeof, types
 from .support import TestCase, CompilationCache
 
 no_pyobj_flags = Flags()
 no_pyobj_flags.set("nrt")
-no_pyobj_flags = [no_pyobj_flags]
+
 
 def sinc(x):
     return np.sinc(x)
 
-def angle(x, deg):
+def angle1(x):
+    return np.angle(x)
+
+def angle2(x, deg):
     return np.angle(x, deg)
 
+
 class TestNPFunctions(TestCase):
     """
     Contains tests and test helpers for numpy methods the are of type
@@ -31,10 +35,9 @@ class TestNPFunctions(TestCase):
     def setUp(self):
         self.ccache = CompilationCache()
 
-    def run_unary_real(self, pyfunc, x_types, x_values,
-        flags=no_pyobj_flags, prec='exact',
-        func_extra_types=None, func_extra_args=None,
-        ignore_sign_on_zero=False, abs_tol=None, **kwargs):
+    def run_unary(self, pyfunc, x_types, x_values, flags=no_pyobj_flags,
+                  func_extra_types=None, func_extra_args=None,
+                  ignore_sign_on_zero=False, abs_tol=None, **kwargs):
         """
         Runs tests for a unary function operating in the numerical real space.
 
@@ -57,103 +60,31 @@ def run_unary_real(self, pyfunc, x_types, x_values,
         x_types and x_values must have the same length
 
         """
-        for f in flags:
-            for tx, vx in zip(x_types, x_values):
-                if func_extra_args == None:
-                    cr = self.ccache.compile(pyfunc, (tx,), flags=f)
-                    cfunc = cr.entry_point
-                    got = cfunc(vx)
-                    expected = pyfunc(vx)
-                    actual_prec = 'single' if tx is types.float32 else prec
-                    msg = 'for input %r with prec %r' % (vx, prec)
-                    self.assertPreciseEqual(got, expected,
-                                            prec=actual_prec, msg=msg,
-                                            ignore_sign_on_zero=
-                                            ignore_sign_on_zero,
-                                            abs_tol=abs_tol, **kwargs)
-                else:
-                    for xtype, xargs in zip(func_extra_types, func_extra_args):
-                        cr = self.ccache.compile(pyfunc, (tx,xtype), flags=f)
-                        cfunc = cr.entry_point
-                        got = cfunc(vx, xargs)
-                        expected = pyfunc(vx, xargs)
-                        actual_prec = 'single' if tx is types.float32 else prec
-                        msg = 'for input %r with prec %r' % (vx, prec)
-                        self.assertPreciseEqual(got, expected,
-                                                prec=actual_prec,
-                                                msg=msg,
-                                                ignore_sign_on_zero=
-                                                ignore_sign_on_zero,
-                                                abs_tol=abs_tol, **kwargs)
-
-    def run_unary_complex(self, pyfunc, x_types, x_values, ulps=1,
-                  func_extra_types=None, func_extra_args=None,
-                  ignore_sign_on_zero=False, abs_tol=None,
-                  flags=no_pyobj_flags):
-        """
-        Runs tests for a unary function operating in the numerical complex
-        space.
-
-        Parameters
-        ----------
-        pyfunc : a python function definition holding that calls the numpy
-                 functions to be tested.
-        x_types: the types of the values being tested, see numba.types
-        x_values: the numerical values of the values to be tested
-        ulps: the number of ulps in error considered acceptable
-        flags: flags to pass to the CompilationCache::ccache::compile function
-        func_extra_types: the types of additional arguments to the numpy
-                          function
-        func_extra_args:  additional arguments to the numpy function
-        ignore_sign_on_zero: boolean as to whether to allow zero values
-        with incorrect signs to be considered equal
-        prec: the required precision match, see assertPreciseEqual
-
-        Notes:
-        ------
-        x_types and x_values must have the same length
-        """
-        for f in flags:
-            for tx in x_types:
-                if(func_extra_args == None):
-                    cr = self.ccache.compile(pyfunc, (tx,), flags=f)
-                    cfunc = cr.entry_point
-                    prec = 'single' if tx in (types.float32, types.complex64) \
-                        else 'double'
-                    for vx in x_values:
-                        try:
-                            expected = pyfunc(vx)
-                        except ValueError as e:
-                            self.assertIn("math domain error", str(e))
-                            continue
-                        got = cfunc(vx)
-                        msg = 'for input %r with prec %r' % (vx, prec)
-                        self.assertPreciseEqual(got, expected, prec=prec,
-                                                ulps=ulps, msg=msg,
-                                                ignore_sign_on_zero=
-                                                ignore_sign_on_zero,
-                                                abs_tol=abs_tol)
-                else:
-                    for xtype, xargs in zip(func_extra_types, func_extra_args):
-                        cr = self.ccache.compile(pyfunc, (tx,xtype), flags=f)
-                        cfunc = cr.entry_point
-                        prec = 'single' if tx in (types.float32,\
-                            types.complex64) else 'double'
-                        for vx in x_values:
-                            try:
-                                expected = pyfunc(vx, xargs)
-                            except ValueError as e:
-                                self.assertIn("math domain error", str(e))
-                                continue
-                            got = cfunc(vx, xargs)
-                            msg = 'for input %r with prec %r' % (vx, prec)
-                            self.assertPreciseEqual(got, expected, prec=prec,
-                                                    ulps=ulps, msg=msg,
-                                                    ignore_sign_on_zero=
-                                                    ignore_sign_on_zero,
-                                                    abs_tol=abs_tol)
-
-    def test_sinc(self, flags=no_pyobj_flags):
+        for tx, vx in zip(x_types, x_values):
+            if func_extra_args is None:
+                func_extra_types = func_extra_args = [()]
+            for xtypes, xargs in zip(func_extra_types, func_extra_args):
+                cr = self.ccache.compile(pyfunc, (tx,) + xtypes,
+                                         flags=flags)
+                cfunc = cr.entry_point
+                got = cfunc(vx, *xargs)
+                expected = pyfunc(vx, *xargs)
+                try:
+                    scalty = tx.dtype
+                except AttributeError:
+                    scalty = tx
+                prec = ('single'
+                        if scalty in (types.float32, types.complex64)
+                        else 'double')
+                msg = 'for input %r with prec %r' % (vx, prec)
+                self.assertPreciseEqual(got, expected,
+                                        prec=prec,
+                                        msg=msg,
+                                        ignore_sign_on_zero=
+                                        ignore_sign_on_zero,
+                                        abs_tol=abs_tol, **kwargs)
+
+    def test_sinc(self):
         """
         Tests the sinc() function.
         This test is purely to assert numerical computations are correct.
@@ -177,21 +108,24 @@ def test_sinc(self, flags=no_pyobj_flags):
         # so comparison via ULP is invalid.
         # We therefore opt to assume that values under machine precision are
         # equal in this case.
-        tol="eps"
+        tol = "eps"
 
         pyfunc = sinc
 
+        def check(x_types, x_values, **kwargs):
+            self.run_unary(pyfunc, x_types, x_values,
+                                ignore_sign_on_zero=isoz, abs_tol=tol,
+                                **kwargs)
+
         # real domain scalar context
         x_values = [1., -1., 0.0, -0.0, 0.5, -0.5, 5, -5, 5e-21, -5e-21]
         x_types = [types.float32, types.float64] * (len(x_values) // 2)
-        self.run_unary_real(pyfunc, x_types, x_values, flags,
-                            ignore_sign_on_zero=isoz, abs_tol=tol)
+        check(x_types, x_values)
 
         # real domain vector context
-        x_values = np.array(x_values)
-        x_types = [types.float32, types.float64]
-        self.run_unary_real(pyfunc, x_types, x_values, flags=flags,
-                               ignore_sign_on_zero=isoz, abs_tol=tol)
+        x_values = [np.array(x_values, dtype=np.float64)]
+        x_types = [typeof(v) for v in x_values]
+        check(x_types, x_values)
 
         # complex domain scalar context
         x_values = [1.+0j, -1+0j, 0.0+0.0j, -0.0+0.0j, 0+1j, 0-1j, 0.5+0.0j,
@@ -200,15 +134,12 @@ def test_sinc(self, flags=no_pyobj_flags):
                     5e-21+0j, -5e-21+0j, 5e-21j, +(0-5e-21j)
                     ]
         x_types = [types.complex64, types.complex128] * (len(x_values) // 2)
-        self.run_unary_complex(pyfunc, x_types, x_values, flags=flags,
-                               ignore_sign_on_zero=isoz, abs_tol=tol, ulps=2)
-
+        check(x_types, x_values, ulps=2)
 
         # complex domain vector context
-        x_values = np.array(x_values)
-        x_types = [types.complex64, types.complex128]
-        self.run_unary_complex(pyfunc, x_types, x_values, flags=flags,
-                               ignore_sign_on_zero=isoz, abs_tol=tol, ulps=2)
+        x_values = [np.array(x_values, dtype=np.complex128)]
+        x_types = [typeof(v) for v in x_values]
+        check(x_types, x_values, ulps=2)
 
 
     def test_angle(self, flags=no_pyobj_flags):
@@ -216,32 +147,36 @@ def test_angle(self, flags=no_pyobj_flags):
         Tests the angle() function.
         This test is purely to assert numerical computations are correct.
         """
-        pyfunc = angle
+        pyfunc1 = angle1
+        pyfunc2 = angle2
+
+        def check(x_types, x_values):
+            # angle(x)
+            self.run_unary(pyfunc1, x_types, x_values)
+            # angle(x, deg)
+            xtra_values = [(True,), (False,)]
+            xtra_types = [(types.bool_,)] * len(xtra_values)
+            self.run_unary(pyfunc2, x_types, x_values,
+                           func_extra_types=xtra_types,
+                           func_extra_args=xtra_values,)
 
         # real domain scalar context
         x_values = [1., -1., 0.0, -0.0, 0.5, -0.5, 5, -5]
-        x_types = [types.float32, types.float64] * (len(x_values) // 2)
-        xtra_values = [True, False]
-        xtra_types = [types.bool_, types.bool_]
-        self.run_unary_real(pyfunc, x_types, x_values,
-             flags, func_extra_types=xtra_types, func_extra_args=xtra_values)
+        x_types = [types.float32, types.float64] * (len(x_values) // 2 + 1)
+        check(x_types, x_values)
 
         # real domain vector context
-        x_values = np.array(x_values)
-        x_types = [types.float32, types.float64]
-        self.run_unary_real(pyfunc, x_types, x_values,
-             flags, func_extra_types=xtra_types, func_extra_args=xtra_values)
+        x_values = [np.array(x_values, dtype=np.float64)]
+        x_types = [typeof(v) for v in x_values]
+        check(x_types, x_values)
 
         # complex domain scalar context
         x_values = [1.+0j, -1+0j, 0.0+0.0j, -0.0+0.0j, 1j, -1j, 0.5+0.0j,
                     -0.5+0.0j, 0.5+0.5j, -0.5-0.5j, 5+5j, -5-5j]
-        x_types = [types.complex64, types.complex128] * (len(x_values) // 2)
-        self.run_unary_complex(pyfunc, x_types, x_values,
-              func_extra_types=xtra_types, func_extra_args=xtra_values,
-              flags=flags)
+        x_types = [types.complex64, types.complex128] * (len(x_values) // 2 + 1)
+        check(x_types, x_values)
 
         # complex domain vector context
         x_values = np.array(x_values)
         x_types = [types.complex64, types.complex128]
-        self.run_unary_real(pyfunc, x_types, x_values,
-             flags, func_extra_types=xtra_types, func_extra_args=xtra_values)
+        check(x_types, x_values)