Repository: numba/numba

Issue:
Attributes of Nested Arrays don't work
The following:

``` python
import numpy as np
from numba import njit

dtype = np.dtype([('a', np.int32), ('b', np.float32, 2)])

@njit
def nested_arr_size(rec):
    return rec.b.size

rec = np.zeros(1, dtype)[0]
print(nested_arr_size(rec))
```

Results in the traceback:

```
$ python test.py 
Traceback (most recent call last):
  File "/home/gmarkall/work/numba/numba/typing/context.py", line 84, in resolve_getattr
    attrinfo = self.attributes[value]
KeyError: nestedarray(float32, (2,))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/gmarkall/work/numba/numba/typeinfer.py", line 269, in __call__
    attrty = context.resolve_getattr(value=ty, attr=self.attr)
  File "/home/gmarkall/work/numba/numba/typing/context.py", line 87, in resolve_getattr
    attrinfo = self.attributes[type(value)]
KeyError: <class 'numba.types.NestedArray'>

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "test.py", line 11, in <module>
    print(nested_arr_size(rec))
  File "/home/gmarkall/work/numba/numba/dispatcher.py", line 157, in _compile_for_args
    return self.compile(sig)
  File "/home/gmarkall/work/numba/numba/dispatcher.py", line 299, in compile
    flags=flags, locals=self.locals)
  File "/home/gmarkall/work/numba/numba/compiler.py", line 554, in compile_extra
    return pipeline.compile_extra(func)
  File "/home/gmarkall/work/numba/numba/compiler.py", line 300, in compile_extra
    return self.compile_bytecode(bc, func_attr=self.func_attr)
  File "/home/gmarkall/work/numba/numba/compiler.py", line 308, in compile_bytecode
    return self._compile_bytecode()
  File "/home/gmarkall/work/numba/numba/compiler.py", line 541, in _compile_bytecode
    return pm.run(self.status)
  File "/home/gmarkall/work/numba/numba/compiler.py", line 198, in run
    raise patched_exception
  File "/home/gmarkall/work/numba/numba/compiler.py", line 190, in run
    res = stage()
  File "/home/gmarkall/work/numba/numba/compiler.py", line 396, in stage_nopython_frontend
    self.locals)
  File "/home/gmarkall/work/numba/numba/compiler.py", line 674, in type_inference_stage
    infer.propagate()
  File "/home/gmarkall/work/numba/numba/typeinfer.py", line 396, in propagate
    self.constrains.propagate(self.context, self.typevars)
  File "/home/gmarkall/work/numba/numba/typeinfer.py", line 112, in propagate
    constrain(context, typevars)
  File "/home/gmarkall/work/numba/numba/typeinfer.py", line 273, in __call__
    raise TypingError(msg, loc=self.inst.loc)
numba.typeinfer.TypingError: Failed at nopython (nopython frontend)
Unknown attribute 'size' for nestedarray(float32, (2,)) $0.2 $0.3 = getattr(attr=size, value=$0.2)
File "test.py", line 8
```

This needs an additional class for resolving types of all `NestedArray` attributes to be added in `numba/typing/builtins.py` similar to the `ArrayAttribute` class.



Discussion:

Resolving patch:
diff --git a/numba/types.py b/numba/types.py
--- a/numba/types.py
+++ b/numba/types.py
@@ -818,7 +818,7 @@ def strides(self):
         for i in reversed(self._shape):
              strides.append(stride)
              stride *= i
-        return tuple(strides)
+        return tuple(reversed(strides))
 
 
 class UniTuple(IterableType):
diff --git a/numba/typing/builtins.py b/numba/typing/builtins.py
--- a/numba/typing/builtins.py
+++ b/numba/typing/builtins.py
@@ -469,9 +469,6 @@ def resolve_strides(self, ary):
     def resolve_ndim(self, ary):
         return types.intp
 
-    # def resolve_flatten(self, ary):
-    #     return types.Method(Array_flatten, ary)
-
     def resolve_size(self, ary):
         return types.intp
 
@@ -485,6 +482,11 @@ def generic_resolve(self, ary, attr):
                                    layout='A')
 
 
+@builtin_attr
+class NestedArrayAttribute(ArrayAttribute):
+    key = types.NestedArray
+
+
 def generic_homog(self, args, kws):
     assert not args
     assert not kws


Test patch:
diff --git a/numba/tests/test_array_attr.py b/numba/tests/test_array_attr.py
--- a/numba/tests/test_array_attr.py
+++ b/numba/tests/test_array_attr.py
@@ -2,6 +2,7 @@
 import numba.unittest_support as unittest
 import numpy as np
 from numba.compiler import compile_isolated, Flags
+from numba.numpy_support import from_dtype
 from numba import types
 
 
@@ -25,48 +26,110 @@ def array_size(a):
     return a.size
 
 
+def nested_array_itemsize(a):
+    return a.f.itemsize
+
+
+def nested_array_shape(a):
+    return a.f.shape
+
+
+def nested_array_strides(a):
+    return a.f.strides
+
+
+def nested_array_ndim(a):
+    return a.f.ndim
+
+
+def nested_array_size(a):
+    return a.f.size
+
+
 class TestArrayAttr(unittest.TestCase):
+    def setUp(self):
+        self.a = np.arange(10).reshape(2, 5)
+
+    def get_cfunc(self, pyfunc, argspec):
+        # Need to keep a reference to the compile result for the
+        # wrapper function object to remain valid (!)
+        self.__cres = compile_isolated(pyfunc, argspec)
+        return self.__cres.entry_point
+
     def test_shape(self):
         pyfunc = array_shape
-        cres = compile_isolated(pyfunc, (types.int32[:,:], types.int32))
-        cfunc = cres.entry_point
+        cfunc = self.get_cfunc(pyfunc, (types.int32[:,:], types.int32))
 
-        a = np.arange(10).reshape(2, 5)
-        for i in range(a.ndim):
-            self.assertEqual(pyfunc(a, i), cfunc(a, i))
+        for i in range(self.a.ndim):
+            self.assertEqual(pyfunc(self.a, i), cfunc(self.a, i))
 
     def test_strides(self):
         pyfunc = array_strides
-        cres = compile_isolated(pyfunc, (types.int32[:,:], types.int32))
-        cfunc = cres.entry_point
+        cfunc = self.get_cfunc(pyfunc, (types.int32[:,:], types.int32))
 
-        a = np.arange(10).reshape(2, 5)
-        for i in range(a.ndim):
-            self.assertEqual(pyfunc(a, i), cfunc(a, i))
+        for i in range(self.a.ndim):
+            self.assertEqual(pyfunc(self.a, i), cfunc(self.a, i))
 
     def test_ndim(self):
         pyfunc = array_ndim
-        cres = compile_isolated(pyfunc, (types.int32[:,:],))
-        cfunc = cres.entry_point
+        cfunc = self.get_cfunc(pyfunc, (types.int32[:,:],))
 
-        a = np.arange(10).reshape(2, 5)
-        self.assertEqual(pyfunc(a), cfunc(a))
+        self.assertEqual(pyfunc(self.a), cfunc(self.a))
 
     def test_size(self):
         pyfunc = array_size
-        cres = compile_isolated(pyfunc, (types.int32[:,:],))
-        cfunc = cres.entry_point
+        cfunc = self.get_cfunc(pyfunc, (types.int32[:,:],))
 
-        a = np.arange(10).reshape(2, 5)
-        self.assertEqual(pyfunc(a), cfunc(a))
+        self.assertEqual(pyfunc(self.a), cfunc(self.a))
 
     def test_itemsize(self):
         pyfunc = array_itemsize
-        cres = compile_isolated(pyfunc, (types.int32[:,:],))
-        cfunc = cres.entry_point
+        cfunc = self.get_cfunc(pyfunc, (types.int32[:,:],))
+
+        self.assertEqual(pyfunc(self.a), cfunc(self.a))
+
+
+class TestNestedArrayAttr(unittest.TestCase):
+    def setUp(self):
+        dtype = np.dtype([('a', np.int32), ('f', np.int32, (2, 5))])
+        self.a = np.recarray(1, dtype)[0]
+        self.nbrecord = from_dtype(self.a.dtype)
+
+    def get_cfunc(self, pyfunc):
+        # Need to keep a reference to the compile result for the
+        # wrapper function object to remain valid (!)
+        self.__cres = compile_isolated(pyfunc, (self.nbrecord,))
+        return self.__cres.entry_point
+
+    def test_shape(self):
+        pyfunc = nested_array_shape
+        cfunc = self.get_cfunc(pyfunc)
+
+        self.assertEqual(pyfunc(self.a), cfunc(self.a))
+
+    def test_strides(self):
+        pyfunc = nested_array_strides
+        cfunc = self.get_cfunc(pyfunc)
+
+        self.assertEqual(pyfunc(self.a), cfunc(self.a))
+
+    def test_ndim(self):
+        pyfunc = nested_array_ndim
+        cfunc = self.get_cfunc(pyfunc)
+
+        self.assertEqual(pyfunc(self.a), cfunc(self.a))
+
+    def test_size(self):
+        pyfunc = nested_array_size
+        cfunc = self.get_cfunc(pyfunc)
+
+        self.assertEqual(pyfunc(self.a), cfunc(self.a))
+
+    def test_itemsize(self):
+        pyfunc = nested_array_itemsize
+        cfunc = self.get_cfunc(pyfunc)
 
-        a = np.arange(10).reshape(2, 5)
-        self.assertEqual(pyfunc(a), cfunc(a))
+        self.assertEqual(pyfunc(self.a), cfunc(self.a))
 
 
 if __name__ == '__main__':