Repository: numba/numba

Issue:
Better compilation errors for guvectorize
The following code throws an unhelpful error when compiling:

``` python
In [1]: import numba as nb

In [2]: @nb.guvectorize('f8, f8, f8[:], f8[:], f8[:]', '(), (), (s), (s) -> (s)')
   ...: def foo(a, b, c, d, out):
   ...:     for n in range(c.shape[0]):
   ...:         out[n] = a + b + c + d[n]
   ...:
<big traceback>
AttributeError: 'Float' object has no attribute 'as_array'
```

The issue here is that `c` should be `c[n]`, but this is not apparent from the error.



Discussion:
The error here stems from the fact that the gufunc is compiled in object mode, but Numba doesn't seem to support non-array arguments to object mode gufuncs.

You get the same error with the legitimate:

```
@guvectorize('f8, f8, f8[:], f8[:], f8[:]', '(), (), (s), (s) -> (s)',
             forceobj=True)
def foo(a, b, c, d, out):
    for n in range(c.shape[0]):
        out[n] = a + b + c[n] + d[n]
```


Resolving patch:
diff --git a/numba/npyufunc/wrappers.py b/numba/npyufunc/wrappers.py
--- a/numba/npyufunc/wrappers.py
+++ b/numba/npyufunc/wrappers.py
@@ -441,46 +441,55 @@ def _prepare_call_to_object_mode(context, builder, pyapi, func,
     # Convert each llarray into pyobject
     error_pointer = cgutils.alloca_once(builder, Type.int(1), name='error')
     builder.store(cgutils.true_bit, error_pointer)
-    ndarray_pointers = []
-    ndarray_objects = []
-    for i, (arr, arrtype) in enumerate(zip(args, signature.args)):
-        ptr = cgutils.alloca_once(builder, ll_pyobj)
-        ndarray_pointers.append(ptr)
 
-        builder.store(Constant.null(ll_pyobj), ptr)   # initialize to NULL
-
-        arycls = context.make_array(arrtype)
-        array = arycls(context, builder, value=arr)
-
-        zero = Constant.int(ll_int, 0)
-
-        # Extract members of the llarray
-        nd = Constant.int(ll_int, arrtype.ndim)
-        dims = builder.gep(array._get_ptr_by_name('shape'), [zero, zero])
-        strides = builder.gep(array._get_ptr_by_name('strides'), [zero, zero])
-        data = builder.bitcast(array.data, ll_voidptr)
-        dtype = np.dtype(str(arrtype.dtype))
-
-        # Prepare other info for reconstruction of the PyArray
-        type_num = Constant.int(ll_int, dtype.num)
-        itemsize = Constant.int(ll_int, dtype.itemsize)
+    # The PyObject* arguments to the kernel function
+    object_args = []
+    object_pointers = []
+
+    for i, (arg, argty) in enumerate(zip(args, signature.args)):
+        # Allocate NULL-initialized slot for this argument
+        objptr = cgutils.alloca_once(builder, ll_pyobj, zfill=True)
+        object_pointers.append(objptr)
+
+        if isinstance(argty, types.Array):
+            # Special case arrays: we don't need full-blown NRT reflection
+            # since the argument will be gone at the end of the kernel
+            arycls = context.make_array(argty)
+            array = arycls(context, builder, value=arg)
+
+            zero = Constant.int(ll_int, 0)
+
+            # Extract members of the llarray
+            nd = Constant.int(ll_int, argty.ndim)
+            dims = builder.gep(array._get_ptr_by_name('shape'), [zero, zero])
+            strides = builder.gep(array._get_ptr_by_name('strides'), [zero, zero])
+            data = builder.bitcast(array.data, ll_voidptr)
+            dtype = np.dtype(str(argty.dtype))
+
+            # Prepare other info for reconstruction of the PyArray
+            type_num = Constant.int(ll_int, dtype.num)
+            itemsize = Constant.int(ll_int, dtype.itemsize)
+
+            # Call helper to reconstruct PyArray objects
+            obj = builder.call(fn_array_new, [nd, dims, strides, data,
+                                              type_num, itemsize])
+        else:
+            # Other argument types => use generic boxing
+            obj = pyapi.from_native_value(argty, arg)
 
-        # Call helper to reconstruct PyArray objects
-        obj = builder.call(fn_array_new, [nd, dims, strides, data,
-                                          type_num, itemsize])
-        builder.store(obj, ptr)
-        ndarray_objects.append(obj)
+        builder.store(obj, objptr)
+        object_args.append(obj)
 
         obj_is_null = cgutils.is_null(builder, obj)
         builder.store(obj_is_null, error_pointer)
         cgutils.cbranch_or_continue(builder, obj_is_null, bb_core_return)
 
     # Call ufunc core function
-    object_sig = [types.pyobject] * len(ndarray_objects)
+    object_sig = [types.pyobject] * len(object_args)
 
     status, retval = context.call_conv.call_function(
         builder, func, types.pyobject, object_sig,
-        ndarray_objects, env=env)
+        object_args, env=env)
     builder.store(status.is_error, error_pointer)
 
     # Release returned object
@@ -490,9 +499,9 @@ def _prepare_call_to_object_mode(context, builder, pyapi, func,
     # At return block
     builder.position_at_end(bb_core_return)
 
-    # Release argument object
-    for ndary_ptr in ndarray_pointers:
-        pyapi.decref(builder.load(ndary_ptr))
+    # Release argument objects
+    for objptr in object_pointers:
+        pyapi.decref(builder.load(objptr))
 
     innercall = status.code
     return innercall, builder.load(error_pointer)


Test patch:
diff --git a/numba/tests/npyufunc/test_ufuncbuilding.py b/numba/tests/npyufunc/test_ufuncbuilding.py
--- a/numba/tests/npyufunc/test_ufuncbuilding.py
+++ b/numba/tests/npyufunc/test_ufuncbuilding.py
@@ -55,6 +55,13 @@ def guadd_obj(a, b, c):
         for j in range(y):
             c[i, j] = a[i, j] + b[i, j]
 
+def guadd_scalar_obj(a, b, c):
+    Dummy()  # to force object mode
+    x, y = c.shape
+    for i in range(x):
+        for j in range(y):
+            c[i, j] = a[i, j] + b
+
 
 class MyException(Exception):
     pass
@@ -290,6 +297,16 @@ def test_guvectorize_objectmode(self):
         b = ufunc(a, a)
         self.assertTrue(numpy.all(a + a == b))
 
+    def test_guvectorize_scalar_objectmode(self):
+        """
+        Test passing of scalars to object mode gufuncs.
+        """
+        ufunc = guvectorize(['(int32[:,:], int32, int32[:,:])'],
+                            "(x,y),()->(x,y)")(guadd_scalar_obj)
+        a = numpy.arange(10, dtype='int32').reshape(2, 5)
+        b = ufunc(a, 3)
+        self.assertTrue(numpy.all(a + 3 == b))
+
     def test_guvectorize_error_in_objectmode(self):
         ufunc = guvectorize(['(int32[:,:], int32[:,:], int32[:,:])'],
                             "(x,y),(x,y)->(x,y)", forceobj=True)(guerror)