Repository: numba/numba
Pull request: Fix casting in namedtuple ctor.
As title, sorts out literals in ctor args but types
in the ctor return type. Also fixes a bug in closure
inlining which incorrectly assumes a value is a string.

Fixes #3813

<!--

Thanks for wanting to contribute to Numba :)

First, if you need some help or want to chat to the core developers, please
visit https://gitter.im/numba/numba for real time chat or post to the Numba
mailing list https://groups.google.com/a/continuum.io/forum/#!forum/numba-users.

Here's some guidelines to help the review process go smoothly.

0. Please write a description in this text box of the changes that are being
   made.

1. Please ensure that you have written units tests for the changes made/features
   added.

2. If you are closing an issue please use one of the automatic closing words as
   noted here: https://help.github.com/articles/closing-issues-using-keywords/

3. If your pull request is not ready for review but you want to make use of the
   continuous integration testing facilities please label it with `[WIP]` and
   then remove the label when you'd like it to be reviewed.

4. Once review has taken place please do not add features or make changes out of
   the scope of those requested by the reviewer (doing this just add delays as
   already reviewed code ends up having to be re-reviewed/it is hard to tell
   what is new etc!). Further, please do not rebase your branch on master/force
   push/rewrite history, doing any of these causes the context of any comments
   made by reviewers to be lost. If conflicts occur against master they should
   be resolved by merging master into the branch used for making the pull
   request.

Many thanks in advance for your cooperation!

-->


Problem statement:
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?

Hints from issue 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
```
@ucasFL the issue is that `value` is an instance of a class (a property object in this case) and the code expect it to be a string so that `getattr/hasattr` works. A fix is here https://github.com/stuartarchibald/numba/commit/cedab73ac9e052b657bae4a35150daa2102a0a43

Commit message: Fix casting in namedtuple ctor.

As title, sorts out literals in ctor args but types
in the ctor return type. Also fixes a bug in closure
inlining which incorrectly assumes a value is a string.

Fixes #3813

Patch to review:
diff --git a/numba/ir_utils.py b/numba/ir_utils.py
index 33de75d9166..4d9d7e380cf 100644
--- 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 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
index e4ee60f6661..eb73da2ed1f 100644
--- 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)
 
diff --git a/numba/tests/test_tuples.py b/numba/tests/test_tuples.py
index 13c36ebbc63..aec5c648aa7 100644
--- 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):


Code review comments:
diff_hunk: @@ -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 isinstance(value, str):
path: numba/ir_utils.py
text: @user1:
The original check that `bool(value) == False` is lost.  Is that ok?

@author:
Good point, whilst fairly unlikely, I think it's also needed.

@author:
b60b35f34 resolved