Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0f33f71

Browse files
committed
disable implicit conversion from PyFloat to .NET integer types (fixes #1342)
1 parent e44aa46 commit 0f33f71

8 files changed

+63
-69
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ details about the cause of the failure
2828
to the regular method return value (unless they are passed with `ref` or `out` keyword).
2929
- BREAKING: Drop support for the long-deprecated CLR.* prefix.
3030
- `PyObject` now implements `IEnumerable<PyObject>` in addition to `IEnumerable`
31+
- floating point values passed from Python are no longer silently truncated
32+
when .NET expects an integer [#1342][i1342]
3133

3234
### Fixed
3335

@@ -809,3 +811,4 @@ This version improves performance on benchmarks significantly compared to 2.3.
809811
[i755]: https://github.com/pythonnet/pythonnet/pull/755
810812
[p534]: https://github.com/pythonnet/pythonnet/pull/534
811813
[i449]: https://github.com/pythonnet/pythonnet/issues/449
814+
[i1342]: https://github.com/pythonnet/pythonnet/issues/1342

src/runtime/Python.Runtime.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<Platforms>AnyCPU</Platforms>
55
<RootNamespace>Python.Runtime</RootNamespace>
66
<AssemblyName>Python.Runtime</AssemblyName>
7+
<LangVersion>9.0</LangVersion>
78
<PackageId>pythonnet</PackageId>
89
<PackageLicenseUrl>https://github.com/pythonnet/pythonnet/blob/master/LICENSE</PackageLicenseUrl>
910
<RepositoryUrl>https://github.com/pythonnet/pythonnet</RepositoryUrl>

src/runtime/arrayobject.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public static IntPtr tp_new(IntPtr tpRaw, IntPtr args, IntPtr kw)
5454
// create single dimensional array
5555
if (Runtime.PyInt_Check(op))
5656
{
57-
dimensions[0] = Runtime.PyLong_AsLongLong(op);
57+
dimensions[0] = Runtime.PyLong_AsSignedSize_t(op);
5858
if (dimensions[0] == -1 && Exceptions.ErrorOccurred())
5959
{
6060
Exceptions.Clear();
@@ -89,7 +89,7 @@ static NewReference CreateMultidimensional(Type elementType, long[] dimensions,
8989
return default;
9090
}
9191

92-
dimensions[dimIndex] = Runtime.PyLong_AsLongLong(dimObj);
92+
dimensions[dimIndex] = Runtime.PyLong_AsSignedSize_t(dimObj);
9393
if (dimensions[dimIndex] == -1 && Exceptions.ErrorOccurred())
9494
{
9595
Exceptions.RaiseTypeError("array constructor expects integer dimensions");

src/runtime/converter.cs

Lines changed: 34 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,7 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo
517517
case TypeCode.Int32:
518518
{
519519
// Python3 always use PyLong API
520-
long num = Runtime.PyLong_AsLongLong(value);
520+
nint num = Runtime.PyLong_AsSignedSize_t(value);
521521
if (num == -1 && Exceptions.ErrorOccurred())
522522
{
523523
goto convert_error;
@@ -547,7 +547,7 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo
547547
goto type_error;
548548
}
549549

550-
int num = Runtime.PyLong_AsLong(value);
550+
nint num = Runtime.PyLong_AsSignedSize_t(value);
551551
if (num == -1 && Exceptions.ErrorOccurred())
552552
{
553553
goto convert_error;
@@ -573,7 +573,7 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo
573573
goto type_error;
574574
}
575575

576-
int num = Runtime.PyLong_AsLong(value);
576+
nint num = Runtime.PyLong_AsSignedSize_t(value);
577577
if (num == -1 && Exceptions.ErrorOccurred())
578578
{
579579
goto convert_error;
@@ -610,7 +610,7 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo
610610
}
611611
goto type_error;
612612
}
613-
int num = Runtime.PyLong_AsLong(value);
613+
nint num = Runtime.PyLong_AsSignedSize_t(value);
614614
if (num == -1 && Exceptions.ErrorOccurred())
615615
{
616616
goto convert_error;
@@ -625,7 +625,7 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo
625625

626626
case TypeCode.Int16:
627627
{
628-
int num = Runtime.PyLong_AsLong(value);
628+
nint num = Runtime.PyLong_AsSignedSize_t(value);
629629
if (num == -1 && Exceptions.ErrorOccurred())
630630
{
631631
goto convert_error;
@@ -640,18 +640,35 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo
640640

641641
case TypeCode.Int64:
642642
{
643-
long num = (long)Runtime.PyLong_AsLongLong(value);
644-
if (num == -1 && Exceptions.ErrorOccurred())
643+
if (Runtime.Is32Bit)
645644
{
646-
goto convert_error;
645+
if (!Runtime.PyLong_Check(value))
646+
{
647+
goto type_error;
648+
}
649+
long num = Runtime.PyExplicitlyConvertToInt64(value);
650+
if (num == -1 && Exceptions.ErrorOccurred())
651+
{
652+
goto convert_error;
653+
}
654+
result = num;
655+
return true;
656+
}
657+
else
658+
{
659+
nint num = Runtime.PyLong_AsSignedSize_t(value);
660+
if (num == -1 && Exceptions.ErrorOccurred())
661+
{
662+
goto convert_error;
663+
}
664+
result = (long)num;
665+
return true;
647666
}
648-
result = num;
649-
return true;
650667
}
651668

652669
case TypeCode.UInt16:
653670
{
654-
long num = Runtime.PyLong_AsLong(value);
671+
nint num = Runtime.PyLong_AsSignedSize_t(value);
655672
if (num == -1 && Exceptions.ErrorOccurred())
656673
{
657674
goto convert_error;
@@ -666,43 +683,16 @@ private static bool ToPrimitive(IntPtr value, Type obType, out object result, bo
666683

667684
case TypeCode.UInt32:
668685
{
669-
op = value;
670-
if (Runtime.PyObject_TYPE(value) != Runtime.PyLongType)
671-
{
672-
op = Runtime.PyNumber_Long(value);
673-
if (op == IntPtr.Zero)
674-
{
675-
goto convert_error;
676-
}
677-
}
678-
if (Runtime.Is32Bit || Runtime.IsWindows)
686+
nuint num = Runtime.PyLong_AsUnsignedSize_t(value);
687+
if (num == unchecked((nuint)(-1)) && Exceptions.ErrorOccurred())
679688
{
680-
uint num = Runtime.PyLong_AsUnsignedLong32(op);
681-
if (num == uint.MaxValue && Exceptions.ErrorOccurred())
682-
{
683-
goto convert_error;
684-
}
685-
result = num;
689+
goto convert_error;
686690
}
687-
else
691+
if (num > UInt32.MaxValue)
688692
{
689-
ulong num = Runtime.PyLong_AsUnsignedLong64(op);
690-
if (num == ulong.MaxValue && Exceptions.ErrorOccurred())
691-
{
692-
goto convert_error;
693-
}
694-
try
695-
{
696-
result = Convert.ToUInt32(num);
697-
}
698-
catch (OverflowException)
699-
{
700-
// Probably wasn't an overflow in python but was in C# (e.g. if cpython
701-
// longs are 64 bit then 0xFFFFFFFF + 1 will not overflow in
702-
// PyLong_AsUnsignedLong)
703-
goto overflow;
704-
}
693+
goto overflow;
705694
}
695+
result = (uint)num;
706696
return true;
707697
}
708698

src/runtime/pylong.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ public int ToInt32()
246246
/// </remarks>
247247
public long ToInt64()
248248
{
249-
return Runtime.PyLong_AsLongLong(obj);
249+
return Runtime.PyExplicitlyConvertToInt64(obj);
250250
}
251251
}
252252
}

src/runtime/runtime.cs

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,30 +1254,27 @@ internal static IntPtr PyLong_FromUnsignedLong(object value)
12541254
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
12551255
internal static extern IntPtr PyLong_FromString(string value, IntPtr end, int radix);
12561256

1257-
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
1258-
internal static extern int PyLong_AsLong(IntPtr value);
1259-
12601257
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl,
1261-
EntryPoint = "PyLong_AsUnsignedLong")]
1262-
internal static extern uint PyLong_AsUnsignedLong32(IntPtr value);
1263-
1258+
EntryPoint = "PyLong_AsSize_t")]
1259+
internal static extern nuint PyLong_AsUnsignedSize_t(IntPtr value);
12641260
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl,
1265-
EntryPoint = "PyLong_AsUnsignedLong")]
1266-
internal static extern ulong PyLong_AsUnsignedLong64(IntPtr value);
1267-
1268-
internal static object PyLong_AsUnsignedLong(IntPtr value)
1269-
{
1270-
if (Is32Bit || IsWindows)
1271-
return PyLong_AsUnsignedLong32(value);
1272-
else
1273-
return PyLong_AsUnsignedLong64(value);
1274-
}
1275-
1276-
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
1277-
internal static extern long PyLong_AsLongLong(BorrowedReference value);
1278-
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
1279-
internal static extern long PyLong_AsLongLong(IntPtr value);
1261+
EntryPoint = "PyLong_AsSsize_t")]
1262+
internal static extern nint PyLong_AsSignedSize_t(IntPtr value);
1263+
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl,
1264+
EntryPoint = "PyLong_AsSsize_t")]
1265+
internal static extern nint PyLong_AsSignedSize_t(BorrowedReference value);
12801266

1267+
/// <summary>
1268+
/// This function is a rename of PyLong_AsLongLong, which has a commonly undesired
1269+
/// behavior to convert everything (including floats) to integer type, before returning
1270+
/// the value as <see cref="Int64"/>.
1271+
///
1272+
/// <para>In most cases you need to check that value is an instance of PyLongObject
1273+
/// before using this function using <see cref="PyLong_Check(IntPtr)"/>.</para>
1274+
/// </summary>
1275+
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl,
1276+
EntryPoint = "PyLong_AsLongLong")]
1277+
internal static extern long PyExplicitlyConvertToInt64(IntPtr value);
12811278
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
12821279
internal static extern ulong PyLong_AsUnsignedLongLong(IntPtr value);
12831280

src/tests/test_conversion.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ def test_uint32_conversion():
343343
ob.UInt32Field = System.UInt32(0)
344344
assert ob.UInt32Field == 0
345345

346-
with pytest.raises(ValueError):
346+
with pytest.raises(TypeError):
347347
ConversionTest().UInt32Field = "spam"
348348

349349
with pytest.raises(TypeError):

src/tests/test_method.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,9 @@ def test_no_object_in_param():
807807
with pytest.raises(TypeError):
808808
MethodTest.TestOverloadedNoObject("test")
809809

810+
with pytest.raises(TypeError):
811+
MethodTest.TestOverloadedNoObject(5.5)
812+
810813

811814
def test_object_in_param():
812815
"""Test regression introduced by #151 in which Object method overloads

0 commit comments

Comments
 (0)