Repository: numba/numba

Issue:
prange with field from jitclass named T fails
<!--

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 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).

`prange(somejitclass.T)` fails while `prange(somejitclass.L)` works.

```
import numba as nb
import numpy as np

parlist = [('L',nb.int32),('T',nb.int32)]
@nb.jitclass(parlist)
class ParClass():
    def __init__(self):
        self.L = 5
        self.T = 5
        
@nb.njit(parallel=True)
def testL(par,X):
    for i in nb.prange(par.L):
        X[i] = 1

@nb.njit(parallel=True)
def testT(par,X):
    for i in nb.prange(par.T):
        X[i] = 1
        
par = ParClass()

X = np.zeros(par.L)

try:
    testL(par,X)
except:
    print('testL could not run')

try:
    testT(par,X)
except:
    print('testT could not run')    

testT(par,X)

```


Discussion:
Thanks for the report. I'm almost certain that this is because the code spots the `.T` as an attr and interprets this as `numpy.ndarray.T` (i.e. `numpy.transpose`). Clearly a bug.
CC @ehsantn @DrTodd13 

Resolving patch:
diff --git a/numba/array_analysis.py b/numba/array_analysis.py
--- a/numba/array_analysis.py
+++ b/numba/array_analysis.py
@@ -1090,7 +1090,7 @@ def _analyze_expr(self, scope, equiv_set, expr):
 
     def _analyze_op_getattr(self, scope, equiv_set, expr):
         # TODO: getattr of npytypes.Record
-        if expr.attr == 'T':
+        if expr.attr == 'T' and self._isarray(expr.value.name):
             return self._analyze_op_call_numpy_transpose(scope, equiv_set, [expr.value], {})
         elif expr.attr == 'shape':
             shape = equiv_set.get_shape(expr.value)


Test patch:
diff --git a/numba/tests/test_array_analysis.py b/numba/tests/test_array_analysis.py
--- a/numba/tests/test_array_analysis.py
+++ b/numba/tests/test_array_analysis.py
@@ -7,7 +7,8 @@
 from collections import namedtuple
 
 from numba import unittest_support as unittest
-from numba import njit, typeof, types, typing, typeof, ir, utils, bytecode
+from numba import (njit, typeof, types, typing, typeof, ir, utils, bytecode,
+    jitclass, prange)
 from .support import TestCase, tag
 from numba.array_analysis import EquivSet, ArrayAnalysis
 from numba.compiler import Pipeline, Flags, _PipelineManager
@@ -22,6 +23,15 @@
 _reason = 'parfors not supported'
 skip_unsupported = unittest.skipIf(_32bit or _windows_py27, _reason)
 
+
+# test class for #3700
+@jitclass([('L', types.int32), ('T', types.int32)])
+class ExampleClass3700(object):
+    def __init__(self, n):
+        self.L = n
+        self.T = n + 1
+
+
 class TestEquivSet(TestCase):
 
     """
@@ -904,5 +914,20 @@ def test_impl():
 
         self.assertEqual(njit(test_impl, parallel=True)(), test_impl())
 
+    @skip_unsupported
+    def test_array_T_issue_3700(self):
+
+        def test_impl(t_obj, X):
+            for i in prange(t_obj.T):
+                X[i] = i
+            return X.sum()
+
+        n = 5
+        t_obj = ExampleClass3700(n)
+        X1 = np.zeros(t_obj.T)
+        X2 = np.zeros(t_obj.T)
+        self.assertEqual(
+            njit(test_impl, parallel=True)(t_obj, X1), test_impl(t_obj, X2))
+
 if __name__ == '__main__':
     unittest.main()