Repository: numba/numba

Issue:
Wrong type coercion on input arguments
If the following snippet, it looks like first calling the function with int arguments then coerces any further float arguments to int:

```
>>> @jit(nopython=True)
... def mpow(a, b):
...     return math.pow(a, b)
... 
>>> 
>>> mpow(0, 1)
0.0
>>> mpow(0, 0.666)
1.0
>>> mpow(0, 1.666)
0.0
```

It doesn't happen if the function is called with float arguments first:

```
>>> @jit(nopython=True)
... def mpow2(a, b):
...     return math.pow(a, b)
... 
>>> mpow2(0, 0.666)
0.0
>>> mpow2(0, 1)
0.0
>>> mpow2(0, 0.666)
0.0
```



Discussion:
The same issue actually happens with other numerical operators (all of them?).

```
>>> @jit
... def add(a, b): return a + b
...
>>> add(1,2)
3L
>>> add(2.5,3.4)
5L
```


Resolving patch:
diff --git a/numba/typeconv/typeconv.py b/numba/typeconv/typeconv.py
--- a/numba/typeconv/typeconv.py
+++ b/numba/typeconv/typeconv.py
@@ -6,10 +6,10 @@ class TypeManager(object):
     def __init__(self):
         self._ptr = _typeconv.new_type_manager()
 
-    def select_overload(self, sig, overloads):
+    def select_overload(self, sig, overloads, allow_unsafe):
         sig = [t._code for t in sig]
         overloads = [[t._code for t in s] for s in overloads ]
-        return _typeconv.select_overload(self._ptr, sig, overloads)
+        return _typeconv.select_overload(self._ptr, sig, overloads, allow_unsafe)
 
     def check_compatible(self, fromty, toty):
         return _typeconv.check_compatible(self._ptr, fromty._code, toty._code)


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
@@ -1,15 +1,22 @@
 from __future__ import print_function, division, absolute_import
+
+import numpy
+
 from numba import unittest_support as unittest
 from numba.special import typeof
 from numba import vectorize, types, jit
-import numpy
+from .support import TestCase
 
 
 def dummy(x):
     return x
 
+def add(x, y):
+    return x + y
+
+
+class TestDispatcher(TestCase):
 
-class TestDispatcher(unittest.TestCase):
     def test_typeof(self):
         self.assertEqual(typeof(numpy.int8(1)), types.int8)
         self.assertEqual(typeof(numpy.uint16(1)), types.uint16)
@@ -40,6 +47,24 @@ def foo(a, b):
         # Exercise the method
         foo.inspect_types()
 
+    def test_coerce_input_types(self):
+        # Issue #486: do not allow unsafe conversions if we can still
+        # compile other specializations.
+        c_add = jit(nopython=True)(add)
+        self.assertPreciseEqual(c_add(123, 456), add(123, 456))
+        self.assertPreciseEqual(c_add(12.3, 45.6), add(12.3, 45.6))
+        self.assertPreciseEqual(c_add(12.3, 45.6j), add(12.3, 45.6j))
+        self.assertPreciseEqual(c_add(12300000000, 456), add(12300000000, 456))
+
+        # Now force compilation of only a single specialization
+        c_add = jit('(i4, i4)', nopython=True)(add)
+        self.assertPreciseEqual(c_add(123, 456), add(123, 456))
+        # Implicit (unsafe) conversion of float to int
+        self.assertPreciseEqual(c_add(12.3, 45.6), add(12, 45))
+        with self.assertRaises(TypeError):
+            # Implicit conversion of complex to int disallowed
+            c_add(12.3, 45.6j)
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/numba/tests/test_typeconv.py b/numba/tests/test_typeconv.py
--- a/numba/tests/test_typeconv.py
+++ b/numba/tests/test_typeconv.py
@@ -7,6 +7,7 @@
 
 
 class TestTypeConv(unittest.TestCase):
+
     def test_typeconv(self):
         tm = TypeManager()
 
@@ -21,10 +22,15 @@ def test_typeconv(self):
         ovs = [
             (i32, i32),
             (f32, f32),
+            (i64, i64),
         ]
 
-        sel = tm.select_overload(sig, ovs)
+        # allow_unsafe = True => a conversion from i32 to f32 is chosen
+        sel = tm.select_overload(sig, ovs, True)
         self.assertEqual(sel, 1)
+        # allow_unsafe = False => no overload available
+        with self.assertRaises(TypeError):
+            sel = tm.select_overload(sig, ovs, False)
 
     def test_default(self):
         tm = rules.default_type_manager
@@ -57,7 +63,35 @@ def test_overload1(self):
             (i32, i32, i32),
             (i64, i64, i64),
         ]
-        print(tm.select_overload(sig, ovs))
+        # The first overload is unsafe, the second is safe => the second
+        # is always chosen, regardless of allow_unsafe.
+        self.assertEqual(tm.select_overload(sig, ovs, True), 1)
+        self.assertEqual(tm.select_overload(sig, ovs, False), 1)
+
+    def test_overload2(self):
+        tm = rules.default_type_manager
+
+        i16 = types.int16
+        i32 = types.int32
+        i64 = types.int64
+
+        sig = (i32, i16, i32)
+        ovs = [
+            # Three promotes
+            (i64, i64, i64),
+            # One promotes, two exact types
+            (i32, i32, i32),
+            # Two unsafe converts, one exact type
+            (i16, i16, i16),
+        ]
+        self.assertEqual(tm.select_overload(sig, ovs, allow_unsafe=False), 1)
+        self.assertEqual(tm.select_overload(sig, ovs, allow_unsafe=True), 1)
+
+        # The same in reverse order
+        ovs.reverse()
+        self.assertEqual(tm.select_overload(sig, ovs, allow_unsafe=False), 1)
+        self.assertEqual(tm.select_overload(sig, ovs, allow_unsafe=True), 1)
+
 
 if __name__ == '__main__':
     unittest.main()