diff --git a/src/runtime/Native/ITypeOffsets.cs b/src/runtime/Native/ITypeOffsets.cs index 2c4fdf59a..0920a312a 100644 --- a/src/runtime/Native/ITypeOffsets.cs +++ b/src/runtime/Native/ITypeOffsets.cs @@ -22,6 +22,7 @@ interface ITypeOffsets int nb_true_divide { get; } int nb_and { get; } int nb_int { get; } + int nb_float { get; } int nb_or { get; } int nb_xor { get; } int nb_lshift { get; } diff --git a/src/runtime/Native/TypeOffset.cs b/src/runtime/Native/TypeOffset.cs index 59c5c3ee0..847f1766d 100644 --- a/src/runtime/Native/TypeOffset.cs +++ b/src/runtime/Native/TypeOffset.cs @@ -31,6 +31,7 @@ static partial class TypeOffset internal static int nb_or { get; private set; } internal static int nb_xor { get; private set; } internal static int nb_int { get; private set; } + internal static int nb_float { get; private set; } internal static int nb_lshift { get; private set; } internal static int nb_rshift { get; private set; } internal static int nb_remainder { get; private set; } diff --git a/src/runtime/Types/ClassBase.cs b/src/runtime/Types/ClassBase.cs index 1e3c325cc..943841e25 100644 --- a/src/runtime/Types/ClassBase.cs +++ b/src/runtime/Types/ClassBase.cs @@ -529,6 +529,37 @@ static NewReference tp_call_impl(BorrowedReference ob, BorrowedReference args, B return callBinder.Invoke(ob, args, kw); } + static NewReference DoConvert(BorrowedReference ob) + { + var self = (CLRObject)GetManagedObject(ob)!; + using var python = self.inst.ToPython(); + return python.NewReferenceOrNull(); + } + + static NewReference DoConvertInt(BorrowedReference ob) + { + var self = (CLRObject)GetManagedObject(ob)!; + return Runtime.PyLong_FromLongLong(Convert.ToInt64(self.inst)); + } + + static NewReference DoConvertUInt(BorrowedReference ob) + { + var self = (CLRObject)GetManagedObject(ob)!; + return Runtime.PyLong_FromUnsignedLongLong(Convert.ToUInt64(self.inst)); + } + + static NewReference DoConvertBooleanInt(BorrowedReference ob) + { + var self = (CLRObject)GetManagedObject(ob)!; + return Runtime.PyInt_FromInt32((bool)self.inst ? 1 : 0); + } + + static NewReference DoConvertFloat(BorrowedReference ob) + { + var self = (CLRObject)GetManagedObject(ob)!; + return Runtime.PyFloat_FromDouble(Convert.ToDouble(self.inst)); + } + static IEnumerable GetCallImplementations(Type type) => type.GetMethods(BindingFlags.Public | BindingFlags.Instance) .Where(m => m.Name == "__call__"); @@ -564,6 +595,29 @@ public virtual void InitializeSlots(BorrowedReference pyType, SlotsHolder slotsH { TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.mp_length, new Interop.B_P(MpLengthSlot.impl), slotsHolder); } + + switch (Type.GetTypeCode(type.Value)) + { + case TypeCode.Boolean: + case TypeCode.SByte: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_int, new Interop.B_N(DoConvertInt), slotsHolder); + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_float, new Interop.B_N(DoConvertFloat), slotsHolder); + break; + case TypeCode.Byte: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_int, new Interop.B_N(DoConvertUInt), slotsHolder); + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_float, new Interop.B_N(DoConvertFloat), slotsHolder); + break; + case TypeCode.Double: + case TypeCode.Single: + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_float, new Interop.B_N(DoConvertFloat), slotsHolder); + break; + } } public virtual bool HasCustomNew() => this.GetType().GetMethod("tp_new") is not null; diff --git a/src/runtime/Types/OperatorMethod.cs b/src/runtime/Types/OperatorMethod.cs index 0c6127f65..88939a51d 100644 --- a/src/runtime/Types/OperatorMethod.cs +++ b/src/runtime/Types/OperatorMethod.cs @@ -53,6 +53,7 @@ static OperatorMethod() ["op_UnaryPlus"] = new SlotDefinition("__pos__", TypeOffset.nb_positive), ["__int__"] = new SlotDefinition("__int__", TypeOffset.nb_int), + ["__float__"] = new SlotDefinition("__float__", TypeOffset.nb_float), }; ComparisonOpMap = new Dictionary { diff --git a/tests/test_conversion.py b/tests/test_conversion.py index f8d8039c6..69e7ec63f 100644 --- a/tests/test_conversion.py +++ b/tests/test_conversion.py @@ -720,3 +720,28 @@ def test_intptr_construction(): with pytest.raises(OverflowError): UIntPtr(v) +def test_explicit_conversion(): + from System import ( + Int64, UInt64, Int32, UInt32, Int16, UInt16, Byte, SByte, Boolean + ) + from System import Double, Single + + assert int(Boolean(False)) == 0 + assert int(Boolean(True)) == 1 + + for t in [UInt64, UInt32, UInt16, Byte]: + assert int(t(127)) == 127 + assert float(t(127)) == 127.0 + + for t in [Int64, Int32, Int16, SByte]: + assert int(t(127)) == 127 + assert int(t(-127)) == -127 + assert float(t(127)) == 127.0 + assert float(t(-127)) == -127.0 + + assert int(Int64.MaxValue) == 2**63 - 1 + assert int(Int64.MinValue) == -2**63 + assert int(UInt64.MaxValue) == 2**64 - 1 + + for t in [Single, Double]: + assert float(t(0.125)) == 0.125