Repository: numba/numba

Issue:
Numba 0.41.0 bug: Cannot unify Literal[int](0) and int64
Upgrading to the latest numba version `0.41.0` broke some old code of mine, which used to work with numba `0.40.1`. I believe the issue boils down to the newly introduced integer literals not working as expected. A minimal example for reproducing the error is

```python
from numba import njit
from typing import NamedTuple

class Data2(NamedTuple):
    a: int
    b: int

@njit
def Data1(value):
    return Data2(value, 0)

@njit
def call(i, j):
    if j == 0:
        result = Data1(i)
    else:
        result = Data2(i, j)

    return result            

call(1, 1)
```

This results in the following error:

```
---------------------------------------------------------------------------
TypingError                               Traceback (most recent call last)
<ipython-input-60-b047fa49e45c> in <module>()
----> 1 call(1, 1)

/opt/local/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/numba/dispatcher.py in _compile_for_args(self, *args, **kws)
    346                 e.patch_message(msg)
    347 
--> 348             error_rewrite(e, 'typing')
    349         except errors.UnsupportedError as e:
    350             # Something unsupported is present in the user code, add help info

/opt/local/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/numba/dispatcher.py in error_rewrite(e, issue_type)
    313                 raise e
    314             else:
--> 315                 reraise(type(e), e, None)
    316 
    317         argtypes = []

/opt/local/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/numba/six.py in reraise(tp, value, tb)
    656             value = tp()
    657         if value.__traceback__ is not tb:
--> 658             raise value.with_traceback(tb)
    659         raise value
    660 

TypingError: Failed in nopython mode pipeline (step: nopython frontend)
Cannot unify Data2(int64, Literal[int](0)) and Data2(int64 x 2) for 'result', defined at <ipython-input-59-68b6396882a7> (14)

File "<ipython-input-59-68b6396882a7>", line 14:
def call(i, j):
    <source elided>
    if j == 0:
        result = Data1(i)
        ^

[1] During: typing of assignment at <ipython-input-59-68b6396882a7> (16)

File "<ipython-input-59-68b6396882a7>", line 16:
def call(i, j):
    <source elided>
    else:
        result = Data2(i, j)
        ^
```

Note that I introduced the work-around `Data1` since default values in `NamedTuple` or classes do not seem to be supported by numba, yet.


Discussion:

Resolving patch:
diff --git a/numba/types/containers.py b/numba/types/containers.py
--- a/numba/types/containers.py
+++ b/numba/types/containers.py
@@ -102,6 +102,17 @@ class MemoryView(Buffer):
     """
 
 
+def is_homogeneous(*tys):
+    """Are the types homogeneous?
+    """
+    if tys:
+        first, tys = tys[0], tys[1:]
+        return not any(t != first for t in tys)
+    else:
+        # *tys* is empty.
+        return False
+
+
 class BaseTuple(ConstSized, Hashable):
     """
     The base class for all tuple types (with a known size).
@@ -112,27 +123,23 @@ def from_types(cls, tys, pyclass=None):
         """
         Instantiate the right tuple type for the given element types.
         """
-        homogeneous = False
-        if tys:
-            first = tys[0]
-            for ty in tys[1:]:
-                if ty != first:
-                    break
-            else:
-                homogeneous = True
-
         if pyclass is not None and pyclass is not tuple:
             # A subclass => is it a namedtuple?
             assert issubclass(pyclass, tuple)
             if hasattr(pyclass, "_asdict"):
+                tys = tuple(map(unliteral, tys))
+                homogeneous = is_homogeneous(*tys)
                 if homogeneous:
-                    return NamedUniTuple(first, len(tys), pyclass)
+                    return NamedUniTuple(tys[0], len(tys), pyclass)
                 else:
                     return NamedTuple(tys, pyclass)
-        if homogeneous:
-            return UniTuple(first, len(tys))
         else:
-            return Tuple(tys)
+            # non-named tuple
+            homogeneous = is_homogeneous(*tys)
+            if homogeneous:
+                return UniTuple(tys[0], len(tys))
+            else:
+                return Tuple(tys)
 
 
 class BaseAnonymousTuple(BaseTuple):


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
@@ -442,6 +442,29 @@ def test_type(self):
             self.assertIs(type(got), type(expected))
             self.assertPreciseEqual(got, expected)
 
+    def test_literal_unification(self):
+        # Test for #3565.
+        @jit(nopython=True)
+        def Data1(value):
+            return Rect(value, -321)
+
+        @jit(nopython=True)
+        def call(i, j):
+            if j == 0:
+                # In the error, `result` is typed to `Rect(int, LiteralInt)`
+                # because of the `-321` literal.  This doesn't match the
+                # `result` type in the other branch.
+                result = Data1(i)
+            else:
+                # `result` is typed to be `Rect(int, int)`
+                result = Rect(i, j)
+            return result
+
+        r = call(123, 1321)
+        self.assertEqual(r, Rect(width=123, height=1321))
+        r = call(123, 0)
+        self.assertEqual(r, Rect(width=123, height=-321))
+
 
 class TestTupleNRT(TestCase, MemoryLeakMixin):
     def test_tuple_add(self):
@@ -586,5 +609,7 @@ def check(pyfunc, arg):
         # Heterogeneous
         check(lambda a: tuple(a), (4, 5.5))
 
+
+
 if __name__ == '__main__':
     unittest.main()