Repository: numba/numba

Issue:
Not support create a namedtuple object in function body in nopython mode?
When I pass a namedtuple as an argument into a function in `nopython` mode, it does work well. such as:
```python
from numba import njit
from collections import namedtuple

Person = namedtuple('Person', ['age', 'name'])
p = Person._make([15, 'Bob'])

@njit(nogil = True)
def test(p):
    print(p.age)
    print(p.name)

if __name__ == '__main__' :
    test(p)
```
However, if I want to create a namedtuple object in function, it can't work, such as:
```python
from numba import njit
from collections import namedtuple

Person = namedtuple('Person', ['age', 'name'])

@njit(nogil = True)
def test():
    p = Person._make([15, 'Bob']) # or p = Person(age = 15, name = 'Bob')
    print(p.age)
    print(p.name)

if __name__ == '__main__' :
    test()
```
Did I use it wrong or not?


Discussion:
numba version: 0.42.0
Thanks for the report. You did not use it incorrectly, I think there's actually two problems in Numba here:

1. That the closure inlining pass has a bug in `ir_utils.find_callname`.

```python
from numba import njit
from collections import namedtuple

Person = namedtuple('Person', ['age', 'name'])

@njit(nogil = True)
def test():
    p = Person._make([15, 3])
    print(p.age)
    print(p.name)

if __name__ == '__main__' :
    test()
```
gives:
```
  File "<path>/numba/numba/ir_utils.py", line 1416, in find_callname
    if (hasattr(numpy, value)
TypeError: Failed in nopython mode pipeline (step: inline calls to locally defined closures)
hasattr(): attribute name must be string
```
fixing this bug locally shows the real problem, which is:
```
numba.errors.TypingError: Failed in nopython mode pipeline (step: nopython frontend)
Unknown attribute '_make' of type class(<class '__main__.Person'>)

File "issue3813.py", line 8:
def test():
    p = Person._make([15, 3])
    ^

[1] During: typing of get attribute at issue3813.py (8)

File "issue3813.py", line 8:
def test():
    p = Person._make([15, 3])
    ^
```
essentially the `namedtuple._make` method is unknown to Numba.

2. The next problem arises when using the standard `namedtuple` constructor and the use of a unicode string as an argument.

This is fine:

```python
from numba import njit
from collections import namedtuple

Person = namedtuple('Person', ['age', 'name'])

@njit(nogil = True)
def test():
    p = Person(age=15, name=3.14159)
    print(p.age)
    print(p.name)

if __name__ == '__main__' :
    test()
```

Where as this:
```python
from numba import njit
from collections import namedtuple

Person = namedtuple('Person', ['age', 'name'])

@njit(nogil = True)
def test():
    p = Person(age=15, name='Bob')
    print(p.age)
    print(p.name)

if __name__ == '__main__' :
    test()
```

gives this:
```
  File "<path>/numba/numba/targets/tupleobj.py", line 19, in namedtuple_constructor
    res = context.make_tuple(builder, sig.return_type, args)
  File "<path>/numba/numba/targets/base.py", line 990, in make_tuple
    tup = builder.insert_value(tup, val, i)
  File "<env>/lib/python3.7/site-packages/llvmlite/ir/builder.py", line 921, in insert_value
    instr = instructions.InsertValue(self.block, agg, value, idx, name=name)
  File "<env>/lib/python3.7/site-packages/llvmlite/ir/instructions.py", line 652, in __init__
    % (typ, list(indices), agg.type, elem.type))
numba.errors.LoweringError: Failed in nopython mode pipeline (step: nopython mode backend)
Can only insert {i8*, i64, i32, i64, i8*, i8*} at [1] in {i64, {i8*, i64, i32, i64, i8*, i8*}}: got i8*

File "issue3813.py", line 8:
def test():
    p = Person(age=15, name='Bob')
    ^

[1] During: lowering "$0.5 = call $0.1(func=$0.1, args=[], kws=[('age', Var($const0.2, issue3813.py (8))), ('name', Var($const0.3, issue3813.py (8)))], vararg=None)" at issue3813.py (8)
```
which indicates the named tuple support for unicode types is lacking.
May I know the details of solving the first bug in advance?
```
File "<path>/numba/numba/ir_utils.py", line 1416, in find_callname
    if (hasattr(numpy, value)
TypeError: Failed in nopython mode pipeline (step: inline calls to locally defined closures)
hasattr(): attribute name must be string
```

Resolving patch:
diff --git a/numba/ir_utils.py b/numba/ir_utils.py
--- a/numba/ir_utils.py
+++ b/numba/ir_utils.py
@@ -1401,7 +1401,7 @@ def find_callname(func_ir, expr, typemap=None, definition_finder=get_definition)
                 if hasattr(callee_def.value, key):
                     value = getattr(callee_def.value, key)
                     break
-            if not value:
+            if not value or not isinstance(value, str):
                 raise GuardException
             attrs.append(value)
             def_val = callee_def.value
diff --git a/numba/targets/tupleobj.py b/numba/targets/tupleobj.py
--- a/numba/targets/tupleobj.py
+++ b/numba/targets/tupleobj.py
@@ -17,7 +17,14 @@
 @lower_builtin(types.NamedTupleClass, types.VarArg(types.Any))
 def namedtuple_constructor(context, builder, sig, args):
     # A namedtuple has the same representation as a regular tuple
-    res = context.make_tuple(builder, sig.return_type, args)
+    # the arguments need casting (lower_cast) from the types in the ctor args
+    # to those in the ctor return type, this is to handle cases such as a
+    # literal present in the args, but a type present in the return type.
+    newargs = []
+    for i, arg in enumerate(args):
+        casted = context.cast(builder, arg, sig.args[i], sig.return_type[i])
+        newargs.append(casted)
+    res = context.make_tuple(builder, sig.return_type, tuple(newargs))
     # The tuple's contents are borrowed
     return impl_ret_borrowed(context, builder, sig.return_type, res)
 


Test patch:
diff --git a/numba/tests/test_tuples.py b/numba/tests/test_tuples.py
--- a/numba/tests/test_tuples.py
+++ b/numba/tests/test_tuples.py
@@ -474,6 +474,17 @@ def call(i, j):
         r = call(123, 0)
         self.assertEqual(r, Rect(width=123, height=-321))
 
+    @unittest.skipIf(utils.PYVERSION < (3, 0), "needs Python 3")
+    def test_string_literal_in_ctor(self):
+        # Test for issue #3813
+
+        @jit(nopython=True)
+        def foo():
+            return Rect(10, 'somestring')
+
+        r = foo()
+        self.assertEqual(r, Rect(width=10, height='somestring'))
+
 
 class TestTupleNRT(TestCase, MemoryLeakMixin):
     def test_tuple_add(self):