diff --git a/CHANGELOG.md b/CHANGELOG.md index 1442075ef..f9dd4907c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ details about the cause of the failure to the regular method return value (unless they are passed with `ref` or `out` keyword). - BREAKING: Drop support for the long-deprecated CLR.* prefix. - `PyObject` now implements `IEnumerable` in addition to `IEnumerable` +- floating point values passed from Python are no longer silently truncated +when .NET expects an integer [#1342][i1342] ### Fixed @@ -807,3 +809,4 @@ This version improves performance on benchmarks significantly compared to 2.3. [i755]: https://github.com/pythonnet/pythonnet/pull/755 [p534]: https://github.com/pythonnet/pythonnet/pull/534 [i449]: https://github.com/pythonnet/pythonnet/issues/449 +[i1342]: https://github.com/pythonnet/pythonnet/issues/1342 diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index acb8efc4e..16317e449 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -4,6 +4,7 @@ AnyCPU Python.Runtime Python.Runtime + 9.0 pythonnet https://github.com/pythonnet/pythonnet/blob/master/LICENSE https://github.com/pythonnet/pythonnet diff --git a/src/runtime/arrayobject.cs b/src/runtime/arrayobject.cs index e6a4bee19..828fad6b2 100644 --- a/src/runtime/arrayobject.cs +++ b/src/runtime/arrayobject.cs @@ -49,7 +49,7 @@ public static IntPtr tp_new(IntPtr tpRaw, IntPtr args, IntPtr kw) // create single dimensional array if (Runtime.PyInt_Check(op)) { - dimensions[0] = Runtime.PyLong_AsLongLong(op); + dimensions[0] = Runtime.PyLong_AsSignedSize_t(op); if (dimensions[0] == -1 && Exceptions.ErrorOccurred()) { Exceptions.Clear(); @@ -84,7 +84,7 @@ static NewReference CreateMultidimensional(Type elementType, long[] dimensions, return default; } - dimensions[dimIndex] = Runtime.PyLong_AsLongLong(dimObj); + dimensions[dimIndex] = Runtime.PyLong_AsSignedSize_t(dimObj); if (dimensions[dimIndex] == -1 && Exceptions.ErrorOccurred()) { Exceptions.RaiseTypeError("array constructor expects integer dimensions"); diff --git a/src/runtime/converter.cs b/src/runtime/converter.cs index 2f3810c58..06b8a1810 100644 --- a/src/runtime/converter.cs +++ b/src/runtime/converter.cs @@ -511,7 +511,7 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo case TypeCode.Int32: { // Python3 always use PyLong API - long num = Runtime.PyLong_AsLongLong(value); + nint num = Runtime.PyLong_AsSignedSize_t(value); if (num == -1 && Exceptions.ErrorOccurred()) { goto convert_error; @@ -541,7 +541,7 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo goto type_error; } - int num = Runtime.PyLong_AsLong(value); + nint num = Runtime.PyLong_AsSignedSize_t(value); if (num == -1 && Exceptions.ErrorOccurred()) { goto convert_error; @@ -567,7 +567,7 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo goto type_error; } - int num = Runtime.PyLong_AsLong(value); + nint num = Runtime.PyLong_AsSignedSize_t(value); if (num == -1 && Exceptions.ErrorOccurred()) { goto convert_error; @@ -604,7 +604,7 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo } goto type_error; } - int num = Runtime.PyLong_AsLong(value); + nint num = Runtime.PyLong_AsSignedSize_t(value); if (num == -1 && Exceptions.ErrorOccurred()) { goto convert_error; @@ -619,7 +619,7 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo case TypeCode.Int16: { - int num = Runtime.PyLong_AsLong(value); + nint num = Runtime.PyLong_AsSignedSize_t(value); if (num == -1 && Exceptions.ErrorOccurred()) { goto convert_error; @@ -634,18 +634,35 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo case TypeCode.Int64: { - long num = (long)Runtime.PyLong_AsLongLong(value); - if (num == -1 && Exceptions.ErrorOccurred()) + if (Runtime.Is32Bit) { - goto convert_error; + if (!Runtime.PyLong_Check(value)) + { + goto type_error; + } + long num = Runtime.PyExplicitlyConvertToInt64(value); + if (num == -1 && Exceptions.ErrorOccurred()) + { + goto convert_error; + } + result = num; + return true; + } + else + { + nint num = Runtime.PyLong_AsSignedSize_t(value); + if (num == -1 && Exceptions.ErrorOccurred()) + { + goto convert_error; + } + result = (long)num; + return true; } - result = num; - return true; } case TypeCode.UInt16: { - long num = Runtime.PyLong_AsLong(value); + nint num = Runtime.PyLong_AsSignedSize_t(value); if (num == -1 && Exceptions.ErrorOccurred()) { goto convert_error; @@ -660,43 +677,16 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo case TypeCode.UInt32: { - op = value; - if (Runtime.PyObject_TYPE(value) != Runtime.PyLongType) - { - op = Runtime.PyNumber_Long(value); - if (op == IntPtr.Zero) - { - goto convert_error; - } - } - if (Runtime.Is32Bit || Runtime.IsWindows) + nuint num = Runtime.PyLong_AsUnsignedSize_t(value); + if (num == unchecked((nuint)(-1)) && Exceptions.ErrorOccurred()) { - uint num = Runtime.PyLong_AsUnsignedLong32(op); - if (num == uint.MaxValue && Exceptions.ErrorOccurred()) - { - goto convert_error; - } - result = num; + goto convert_error; } - else + if (num > UInt32.MaxValue) { - ulong num = Runtime.PyLong_AsUnsignedLong64(op); - if (num == ulong.MaxValue && Exceptions.ErrorOccurred()) - { - goto convert_error; - } - try - { - result = Convert.ToUInt32(num); - } - catch (OverflowException) - { - // Probably wasn't an overflow in python but was in C# (e.g. if cpython - // longs are 64 bit then 0xFFFFFFFF + 1 will not overflow in - // PyLong_AsUnsignedLong) - goto overflow; - } + goto overflow; } + result = (uint)num; return true; } diff --git a/src/runtime/pylong.cs b/src/runtime/pylong.cs index 0e21c7c49..fdfd26aba 100644 --- a/src/runtime/pylong.cs +++ b/src/runtime/pylong.cs @@ -246,7 +246,7 @@ public int ToInt32() /// public long ToInt64() { - return Runtime.PyLong_AsLongLong(obj); + return Runtime.PyExplicitlyConvertToInt64(obj); } } } diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index f80db04b6..e42dd53e0 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -1251,30 +1251,27 @@ internal static IntPtr PyLong_FromUnsignedLong(object value) [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyLong_FromString(string value, IntPtr end, int radix); - [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] - internal static extern int PyLong_AsLong(IntPtr value); - [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl, - EntryPoint = "PyLong_AsUnsignedLong")] - internal static extern uint PyLong_AsUnsignedLong32(IntPtr value); - + EntryPoint = "PyLong_AsSize_t")] + internal static extern nuint PyLong_AsUnsignedSize_t(IntPtr value); [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl, - EntryPoint = "PyLong_AsUnsignedLong")] - internal static extern ulong PyLong_AsUnsignedLong64(IntPtr value); - - internal static object PyLong_AsUnsignedLong(IntPtr value) - { - if (Is32Bit || IsWindows) - return PyLong_AsUnsignedLong32(value); - else - return PyLong_AsUnsignedLong64(value); - } - - [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] - internal static extern long PyLong_AsLongLong(BorrowedReference value); - [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] - internal static extern long PyLong_AsLongLong(IntPtr value); + EntryPoint = "PyLong_AsSsize_t")] + internal static extern nint PyLong_AsSignedSize_t(IntPtr value); + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "PyLong_AsSsize_t")] + internal static extern nint PyLong_AsSignedSize_t(BorrowedReference value); + /// + /// This function is a rename of PyLong_AsLongLong, which has a commonly undesired + /// behavior to convert everything (including floats) to integer type, before returning + /// the value as . + /// + /// In most cases you need to check that value is an instance of PyLongObject + /// before using this function using . + /// + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "PyLong_AsLongLong")] + internal static extern long PyExplicitlyConvertToInt64(IntPtr value); [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern ulong PyLong_AsUnsignedLongLong(IntPtr value); diff --git a/src/tests/test_conversion.py b/src/tests/test_conversion.py index 313274647..6b152025d 100644 --- a/src/tests/test_conversion.py +++ b/src/tests/test_conversion.py @@ -343,7 +343,7 @@ def test_uint32_conversion(): ob.UInt32Field = System.UInt32(0) assert ob.UInt32Field == 0 - with pytest.raises(ValueError): + with pytest.raises(TypeError): ConversionTest().UInt32Field = "spam" with pytest.raises(TypeError): diff --git a/src/tests/test_method.py b/src/tests/test_method.py index 18eb5af8e..a69cc6f14 100644 --- a/src/tests/test_method.py +++ b/src/tests/test_method.py @@ -807,6 +807,9 @@ def test_no_object_in_param(): with pytest.raises(TypeError): MethodTest.TestOverloadedNoObject("test") + with pytest.raises(TypeError): + MethodTest.TestOverloadedNoObject(5.5) + def test_object_in_param(): """Test regression introduced by #151 in which Object method overloads