Repository: numba/numba

Issue:
Dispatcher bug 
``` python
from numba import jit

@jit
def foo(a, b):
    return 1

# <class 'int'> <class 'float'>
# <class 'float'> <class 'int'>
# <class 'float'> <class 'float'>
# <class 'int'> <class 'int'>

foo(1, 0.1)
foo(0.1, 1)
foo(0.1, 0.1)
foo(1, 1)
```

```
Traceback (most recent call last):
  File "test_disp.py", line 15, in <module>
    foo(1, 1)
  File "/Users/sklam/dev/numba/numba/dispatcher.py", line 161, in _explain_ambiguous
    tuple(self.overloads.keys()), args, kws)
  File "/Users/sklam/dev/numba/numba/typing/templates.py", line 84, in resolve_overload
    if len(args) == len(case.args):
AttributeError: 'tuple' object has no attribute 'args'
```

Dispatcher bug 
``` python
from numba import jit

@jit
def foo(a, b):
    return 1

# <class 'int'> <class 'float'>
# <class 'float'> <class 'int'>
# <class 'float'> <class 'float'>
# <class 'int'> <class 'int'>

foo(1, 0.1)
foo(0.1, 1)
foo(0.1, 0.1)
foo(1, 1)
```

```
Traceback (most recent call last):
  File "test_disp.py", line 15, in <module>
    foo(1, 1)
  File "/Users/sklam/dev/numba/numba/dispatcher.py", line 161, in _explain_ambiguous
    tuple(self.overloads.keys()), args, kws)
  File "/Users/sklam/dev/numba/numba/typing/templates.py", line 84, in resolve_overload
    if len(args) == len(case.args):
AttributeError: 'tuple' object has no attribute 'args'
```



Discussion:
This bug happens when the call is ambiguous due to (int, int) can be promoted to (float, int) or (int, float) versions.

First, we need to fix the _explain_ambiguous call.
Then, the dispatcher should probably be allowed to generate a new version for this case.  Another reason to rethink our overloading resolution.

This bug happens when the call is ambiguous due to (int, int) can be promoted to (float, int) or (int, float) versions.

First, we need to fix the _explain_ambiguous call.
Then, the dispatcher should probably be allowed to generate a new version for this case.  Another reason to rethink our overloading resolution.


Resolving patch:
diff --git a/numba/dispatcher.py b/numba/dispatcher.py
--- a/numba/dispatcher.py
+++ b/numba/dispatcher.py
@@ -157,8 +157,8 @@ def inspect_types(self, file=None):
     def _explain_ambiguous(self, *args, **kws):
         assert not kws, "kwargs not handled"
         args = tuple([self.typeof_pyval(a) for a in args])
-        resolve_overload(self.typingctx, self.py_func,
-                         tuple(self.overloads.keys()), args, kws)
+        sigs = [cr.signature for cr in self._compileinfos.values()]
+        resolve_overload(self.typingctx, self.py_func, sigs, args, kws)
 
     def __repr__(self):
         return "%s(%s)" % (type(self).__name__, self.py_func)


Test patch:
diff --git a/numba/tests/test_dispatcher.py b/numba/tests/test_dispatcher.py
--- a/numba/tests/test_dispatcher.py
+++ b/numba/tests/test_dispatcher.py
@@ -11,12 +11,12 @@
 def dummy(x):
     return x
 
+
 def add(x, y):
     return x + y
 
 
 class TestDispatcher(TestCase):
-
     def test_numba_interface(self):
         """
         Check that vectorize can accept a decorated object.
@@ -59,6 +59,28 @@ def test_coerce_input_types(self):
             # Implicit conversion of complex to int disallowed
             c_add(12.3, 45.6j)
 
+    def test_ambiguous_new_version(self):
+        """Test compiling new version in an ambiguous case
+        """
+
+        @jit
+        def foo(a, b):
+            return a + b
+
+        INT = 1
+        FLT = 1.5
+        self.assertAlmostEqual(foo(INT, FLT), INT + FLT)
+        self.assertEqual(len(foo.overloads), 1)
+        self.assertAlmostEqual(foo(FLT, INT), FLT + INT)
+        self.assertEqual(len(foo.overloads), 2)
+        self.assertAlmostEqual(foo(FLT, FLT), FLT + FLT)
+        self.assertEqual(len(foo.overloads), 3)
+        # The following call is ambiguous because (int, int) can resolve
+        # to (float, int) or (int, float) with equal weight.
+        self.assertAlmostEqual(foo(1, 1), INT + INT)
+        self.assertEqual(len(foo.overloads), 4, "didn't compile a new "
+                                                "version")
+
 
 if __name__ == '__main__':
     unittest.main()