From 953a3c3317c5ff8a5c363bd115de9d9ba4f9eda1 Mon Sep 17 00:00:00 2001 From: eirannejad Date: Fri, 12 Jan 2024 16:41:28 -0800 Subject: [PATCH] generic tests are now passing --- dev/MethodBinder.Tests/MethodBinderTests.cs | 40 +- dev/MethodBinder.Tests/TargetTypeA.cs | 13 + dev/MethodBinder.Tests/TargetTypeD.cs | 30 + dev/MethodBinder/MethodBinder.cs | 30 +- src/runtime/Converter.cs | 119 ++-- src/runtime/MethodBinder.Solver.cs | 644 ++++++++++++-------- src/runtime/MethodBinder.cs | 93 ++- src/runtime/Runtime.cs | 31 +- src/runtime/Types/ArrayObject.cs | 34 +- tests/test_generic.py | 72 ++- 10 files changed, 738 insertions(+), 368 deletions(-) create mode 100644 dev/MethodBinder.Tests/TargetTypeD.cs diff --git a/dev/MethodBinder.Tests/MethodBinderTests.cs b/dev/MethodBinder.Tests/MethodBinderTests.cs index 5c5b0efd4..5eda81913 100644 --- a/dev/MethodBinder.Tests/MethodBinderTests.cs +++ b/dev/MethodBinder.Tests/MethodBinderTests.cs @@ -20,6 +20,7 @@ public abstract class InvokeTests protected static readonly TargetTypeA A = new(); protected static readonly TargetTypeB B = new(); protected static readonly TargetTypeC C = new(); + protected static readonly TargetTypeD D = new(); protected static readonly object[] NoArgs = Array.Empty(); protected static readonly KeywordArgs NoKwargs = new(); @@ -42,6 +43,16 @@ public void TestFooParam_0() public sealed class InvokeTests_1 : InvokeTests { + [Test] + public void TestFooParam_1_ShortAsGeneric() + { + // Foo(int _1) + object[] args = new object[] { (ushort)0 }; + Guid t = A.Foo((ushort)0); + NewReference r = BINDER.Invoke(A, M, args, NoKwargs); + Assert.That(r.Value, Is.EqualTo(t)); + } + [Test] public void TestFooParam_1_Int() { @@ -168,7 +179,6 @@ public void TestFooParam_1_DataC() public sealed class InvokeTests_2 : InvokeTests { - [Test] public void TestFooParam_2_Int_Int() { @@ -247,6 +257,29 @@ public void TestFooParam_2_FloatArray_FloatArray() } } + public sealed class InvokeTests_2_Ambig : InvokeTests + { + [Test] + public void TestFooParam_2_Int16_Int32() + { + // Foo(short _1, short _2) + object[] args = new object[] { (short)0, (int)1 }; + Guid t = D.Foo((short)0, (short)1); + NewReference r = BINDER.Invoke(D, M, args, NoKwargs); + Assert.That(r.Value, Is.EqualTo(t)); + } + + [Test] + public void TestFooParam_2_Int64_Int32() + { + // Foo(long _1, long _2) + object[] args = new object[] { (long)0, 1 }; + Guid t = D.Foo((long)0, 1); + NewReference r = BINDER.Invoke(D, M, args, NoKwargs); + Assert.That(r.Value, Is.EqualTo(t)); + } + } + public sealed class InvokeTests_N : InvokeTests { [Test] @@ -304,11 +337,6 @@ public void TestFooParam_X_Object_TwoIntAsArray() /* * UNUSED GUIDS -return new Guid("5755a950-a11d-11ee-8c90-0242ac120002"); -return new Guid("5755aa86-a11d-11ee-8c90-0242ac120002"); -return new Guid("5755ab9e-a11d-11ee-8c90-0242ac120002"); -return new Guid("5755acac-a11d-11ee-8c90-0242ac120002"); -return new Guid("5755adc4-a11d-11ee-8c90-0242ac120002"); return new Guid("5755aedc-a11d-11ee-8c90-0242ac120002"); return new Guid("5755afea-a11d-11ee-8c90-0242ac120002"); return new Guid("5755b2b0-a11d-11ee-8c90-0242ac120002"); diff --git a/dev/MethodBinder.Tests/TargetTypeA.cs b/dev/MethodBinder.Tests/TargetTypeA.cs index 3c77f1feb..7e483cd82 100644 --- a/dev/MethodBinder.Tests/TargetTypeA.cs +++ b/dev/MethodBinder.Tests/TargetTypeA.cs @@ -1,3 +1,4 @@ +#pragma warning disable CA1822 using System; namespace MethodBinder.Tests @@ -15,6 +16,18 @@ public Guid } // 1 + public Guid + Foo(short _1) + { + return new Guid("5755ab9e-a11d-11ee-8c90-0242ac120002"); + } + + public Guid + Foo(T _1) + { + return new Guid("5755acac-a11d-11ee-8c90-0242ac120002"); + } + public Guid Foo(int _1) { diff --git a/dev/MethodBinder.Tests/TargetTypeD.cs b/dev/MethodBinder.Tests/TargetTypeD.cs new file mode 100644 index 000000000..1ed5c67fe --- /dev/null +++ b/dev/MethodBinder.Tests/TargetTypeD.cs @@ -0,0 +1,30 @@ +#pragma warning disable CA1822 +using System; + +namespace MethodBinder.Tests +{ + using O = System.Runtime.InteropServices.OptionalAttribute; + using D = System.Runtime.InteropServices.DefaultParameterValueAttribute; + + public class TargetTypeD + { + // 2 + public Guid + Foo(short _1, short _2) + { + return new Guid("5755a950-a11d-11ee-8c90-0242ac120002"); + } + + public Guid + Foo(int _1, int _2) + { + return new Guid("5755aa86-a11d-11ee-8c90-0242ac120002"); + } + + public Guid + Foo(long _1, long _2) + { + return new Guid("5755adc4-a11d-11ee-8c90-0242ac120002"); + } + } +} diff --git a/dev/MethodBinder/MethodBinder.cs b/dev/MethodBinder/MethodBinder.cs index 64b14b4b2..725b048c4 100644 --- a/dev/MethodBinder/MethodBinder.cs +++ b/dev/MethodBinder/MethodBinder.cs @@ -186,11 +186,13 @@ public static NewReference Invoke(object instance, if (TryBind(methods, argsRef, kwargsRef, true, out BindSpec ? spec)) { - if (spec!.TryGetArguments(instance, out object?[] arguments)) + if (spec!.TryGetArguments(instance, + out MethodBase method, + out object?[] arguments)) { try { - object? result = spec!.Method.Invoke(instance, arguments); + object? result = method.Invoke(instance, arguments); if (result is null) { @@ -257,8 +259,13 @@ public static NewReference Invoke(object instance, "op_GreaterThan", }; - static Type? GetCLRType(BorrowedReference br, Type _) - => br.Value?.GetType(); + static Type? GetCLRType(BorrowedReference br) => br.Value?.GetType(); + + static bool TryGetManagedValue(BorrowedReference br, + out object? value) + { + return TryCast(br.Value, out value); + } static bool TryGetManagedValue(BorrowedReference br, Type type, out object? value) @@ -266,6 +273,19 @@ static bool TryGetManagedValue(BorrowedReference br, Type type, return TryCast(br.Value, type, out value); } + static bool TryCast(object? source, out object? cast) + { + cast = default; + + if (source is null) + { + return false; + } + + cast = source; + return true; + } + static bool TryCast(object? source, Type to, out object? cast) { cast = default; @@ -339,7 +359,5 @@ static bool IsReverse(MethodBase method) Type leftOperandType = method.GetParameters()[0].ParameterType; return leftOperandType != method.DeclaringType; } - - static bool IsValid(MethodBase _) => true; } } diff --git a/src/runtime/Converter.cs b/src/runtime/Converter.cs index d65cfa1d6..1717535d4 100644 --- a/src/runtime/Converter.cs +++ b/src/runtime/Converter.cs @@ -19,29 +19,64 @@ private Converter() { } - private static readonly Type objectType; - private static readonly Type stringType; - private static readonly Type singleType; - private static readonly Type doubleType; - private static readonly Type int16Type; - private static readonly Type int32Type; - private static readonly Type int64Type; - private static readonly Type boolType; - private static readonly Type typeType; - - static Converter() + private static readonly Type typeType = typeof(Type); + private static readonly Type objectType = typeof(Object); + private static readonly Type boolType = typeof(Boolean); + private static readonly Type singleType = typeof(Single); + private static readonly Type doubleType = typeof(Double); + private static readonly Type int16Type = typeof(Int16); + private static readonly Type int32Type = typeof(Int32); + private static readonly Type int64Type = typeof(Int64); + private static readonly Type uint16Type = typeof(UInt16); + private static readonly Type uint32Type = typeof(UInt32); + private static readonly Type uint64Type = typeof(UInt64); + private static readonly Type stringType = typeof(String); + + internal static Type? GetTypeByAlias(BorrowedReference pyType, BorrowedReference op) { - objectType = typeof(Object); - stringType = typeof(String); - int16Type = typeof(Int16); - int32Type = typeof(Int32); - int64Type = typeof(Int64); - singleType = typeof(Single); - doubleType = typeof(Double); - boolType = typeof(Boolean); - typeType = typeof(Type); - } + if (pyType == Runtime.PyLongType) + { + nint? int32 = Runtime.PyLong_AsSignedSize_t(op); + if (int32 != null) + { + if (int32 > UInt32.MaxValue) + { + return int64Type; + } + return int32Type; + } + nuint? uint32 = Runtime.PyLong_AsUnsignedSize_t(op); + if (uint32 != null) + { + Exceptions.Clear(); + if (uint32 > UInt32.MaxValue) + { + return uint64Type; + } + return uint32Type; + } + + long? int64 = Runtime.PyLong_AsLongLong(op); + if (int64 != null) + { + Exceptions.Clear(); + return int64Type; + } + + ulong? uint64 = Runtime.PyLong_AsUnsignedLongLong(op); + if (uint64 != null) + { + Exceptions.Clear(); + return uint64Type; + } + + Exceptions.Clear(); + Exceptions.SetError(Exceptions.OverflowError, "value too large to convert"); + } + + return GetTypeByAlias(pyType); + } /// /// Given a builtin Python type, return the corresponding CLR type. @@ -74,13 +109,13 @@ internal static BorrowedReference GetPythonTypeByAlias(Type op) if (op == stringType) return Runtime.PyUnicodeType.Reference; - if (op == int16Type) + if (op == int16Type || op == uint16Type) return Runtime.PyLongType.Reference; - if (op == int32Type) + if (op == int32Type || op == uint32Type) return Runtime.PyLongType.Reference; - if (op == int64Type) + if (op == int64Type || op == uint64Type) return Runtime.PyLongType.Reference; if (op == doubleType) @@ -582,8 +617,8 @@ static bool DecodableByUser(Type type) internal static int ToInt32(BorrowedReference value) { - nint num = Runtime.PyLong_AsSignedSize_t(value); - if (num == -1 && Exceptions.ErrorOccurred()) + nint? num = Runtime.PyLong_AsSignedSize_t(value); + if (num is null) { throw PythonException.ThrowLastAsClrException(); } @@ -621,8 +656,8 @@ internal static bool ToPrimitive(BorrowedReference value, Type obType, out objec case TypeCode.Int32: { // Python3 always use PyLong API - nint num = Runtime.PyLong_AsSignedSize_t(value); - if (num == -1 && Exceptions.ErrorOccurred()) + nint? num = Runtime.PyLong_AsSignedSize_t(value); + if (num is null) { goto convert_error; } @@ -664,8 +699,8 @@ internal static bool ToPrimitive(BorrowedReference value, Type obType, out objec goto type_error; } - nint num = Runtime.PyLong_AsSignedSize_t(value); - if (num == -1 && Exceptions.ErrorOccurred()) + nint? num = Runtime.PyLong_AsSignedSize_t(value); + if (num is null) { goto convert_error; } @@ -690,8 +725,8 @@ internal static bool ToPrimitive(BorrowedReference value, Type obType, out objec goto type_error; } - nint num = Runtime.PyLong_AsSignedSize_t(value); - if (num == -1 && Exceptions.ErrorOccurred()) + nint? num = Runtime.PyLong_AsSignedSize_t(value); + if (num is null) { goto convert_error; } @@ -725,8 +760,8 @@ internal static bool ToPrimitive(BorrowedReference value, Type obType, out objec } goto type_error; } - nint num = Runtime.PyLong_AsSignedSize_t(value); - if (num == -1 && Exceptions.ErrorOccurred()) + nint? num = Runtime.PyLong_AsSignedSize_t(value); + if (num is null) { goto convert_error; } @@ -740,8 +775,8 @@ internal static bool ToPrimitive(BorrowedReference value, Type obType, out objec case TypeCode.Int16: { - nint num = Runtime.PyLong_AsSignedSize_t(value); - if (num == -1 && Exceptions.ErrorOccurred()) + nint? num = Runtime.PyLong_AsSignedSize_t(value); + if (num is null) { goto convert_error; } @@ -771,8 +806,8 @@ internal static bool ToPrimitive(BorrowedReference value, Type obType, out objec } else { - nint num = Runtime.PyLong_AsSignedSize_t(value); - if (num == -1 && Exceptions.ErrorOccurred()) + nint? num = Runtime.PyLong_AsSignedSize_t(value); + if (num is null) { goto convert_error; } @@ -783,8 +818,8 @@ internal static bool ToPrimitive(BorrowedReference value, Type obType, out objec case TypeCode.UInt16: { - nint num = Runtime.PyLong_AsSignedSize_t(value); - if (num == -1 && Exceptions.ErrorOccurred()) + nint? num = Runtime.PyLong_AsSignedSize_t(value); + if (num is null) { goto convert_error; } @@ -798,8 +833,8 @@ internal static bool ToPrimitive(BorrowedReference value, Type obType, out objec case TypeCode.UInt32: { - nuint num = Runtime.PyLong_AsUnsignedSize_t(value); - if (num == unchecked((nuint)(-1)) && Exceptions.ErrorOccurred()) + nuint? num = Runtime.PyLong_AsUnsignedSize_t(value); + if (num is null) { goto convert_error; } diff --git a/src/runtime/MethodBinder.Solver.cs b/src/runtime/MethodBinder.Solver.cs index 7312a3cf7..90cc13805 100644 --- a/src/runtime/MethodBinder.Solver.cs +++ b/src/runtime/MethodBinder.Solver.cs @@ -40,18 +40,19 @@ larger number of parameters. The groups are: FUNCTION GROUP: Kind of function e.g. static vs instance - ARGS GROUP: Dedicated slot for each parameter. See BindSpec.MAX_ARGS + (*) methods here are method with no generic parameters. + ARGS GROUP: Dedicated slot for each parameter. See MethodBinder.MAX_ARGS TYPE GROUP: Kind of Type e.g. ByRef MATCH GROUP: Kind of type match e.g. an exact match has a lower distance MIN ┌───────────────┐ ┌───┐ ┌────────┐ ┌──────────────┐ - │ │ ───── │ │ ───── │ │ ───── │ │ + │ │ ────► │ │ ────► │ │ ───── │ │ D │ INSTANCE │ │ │ │ │ │ EXACT │ I │ │ ──┐ │ │ ──┐ │ │ ──┐ │ │ S ├───────────────┤ │ ├───┤ │ │ │ │ ├──────────────┤ T │ │ │ │┼┼┼│ │ │ │ │ │ │ - A │ INSTANCE │ │ │┼┼┼│ │ │ │ │ │ DERIVED │ + A │ INSTANCE* │ │ │┼┼┼│ │ │ │ │ │ DERIVED │ N │ │ │ │┼┼┼│ │ │ │ │ │ │ C ├───────────────┤ │ │┼┼┼│ │ ├────────┤ │ ├──────────────┤ E │ │ │ │┼┼┼│ │ │ │ │ │ │ @@ -59,8 +60,8 @@ larger number of parameters. R │ │ │ │┼┼┼│ │ │ │ │ │ │ A ├───────────────┤ │ │┼┼┼│ │ │ BY REF │ │ ├──────────────┤ N │ │ │ │┼┼┼│ │ │ │ │ │ │ - G │ STATIC │ │ │┼┼┼│ │ │ │ │ │ CAST/CONVERT │ - E │ │ └── │┼┼┼│ └── │ │ └── │ │ + G │ STATIC* │ │ │┼┼┼│ │ │ │ │ │ CAST/CONVERT │ + E │ │ └─► │┼┼┼│ └── │ │ └── │ │ └───────────────┘ └───┘ └────────┘ └──────────────┘ FUNCTION MAX ARGS TYPE MATCH GROUP GROUP GROUP GROUP @@ -68,14 +69,6 @@ GROUP GROUP GROUP GROUP */ partial class MethodBinder { - static readonly Type PAAT = typeof(ParamArrayAttribute); - static readonly Type UNINT = typeof(nuint); - static readonly Type NINT = typeof(nint); - -#if METHODBINDER_SOLVER_NEW_CACHE_DIST - static readonly Dictionary s_distMap = new(); -#endif - private enum BindParamKind { Argument, @@ -93,7 +86,9 @@ private sealed class BindParam public readonly string Key; public readonly Type Type; - public object? Value { get; set; } = default; + public object? Value { get; private set; } = default; + + public uint Distance { get; private set; } = uint.MaxValue; public BindParam(ParameterInfo paramInfo, BindParamKind kind) { @@ -115,6 +110,21 @@ public BindParam(ParameterInfo paramInfo, BindParamKind kind) } } + public void AssignValue(BorrowedReference item, bool computeDist) + { + Value = new PyObject(item); + + if (computeDist) + { + Distance = GetTypeDistance(item, Type); + } + } + + public void AssignParamsValue(PyObject[] items) + { + Value = items; + } + public bool TryConvert(out object? value) { // NOTE: @@ -122,6 +132,17 @@ public bool TryConvert(out object? value) // or a PyObject[] for a capturing params[] object? v = Value; + if (Type.IsGenericParameter) + { + if (v is null) + { + value = null; + return true; + } + + return TryGetManagedValue((PyObject)v, out value); + } + if (v is null) { if (Kind == BindParamKind.Option) @@ -193,48 +214,43 @@ public bool TryConvert(out object? value) private sealed class BindSpec { - // https://stackoverflow.com/a/17268854/2350244 - // assumes two groups of parameters, hence *2 - // Foo(params) - static readonly uint MAX_ARGS = - Convert.ToUInt32(Math.Pow(2, 16)) * 2; - - static readonly uint TOTAL_MAX_DIST = uint.MaxValue; - static readonly uint FUNC_MAX_DIST = TOTAL_MAX_DIST / 4; - static readonly uint ARG_MAX_DIST = FUNC_MAX_DIST / MAX_ARGS; - static readonly uint TYPE_MAX_DIST = ARG_MAX_DIST / 4; - static readonly uint MATCH_MAX_DIST = TYPE_MAX_DIST / 4; - public static bool IsRedirected(MethodBase method) => method.GetCustomAttributes().Any(); public readonly MethodBase Method; public readonly uint Required; public readonly uint Optional; - public readonly bool Expands; public readonly BindParam[] Parameters; public BindSpec(MethodBase method, uint required, uint optional, - bool expands, BindParam[] argSpecs) { Method = method; Required = required; Optional = optional; - Expands = expands; Parameters = argSpecs; } public bool TryGetArguments(object? instance, + out MethodBase method, out object?[] arguments) { + method = Method; arguments = new object?[Parameters.Length]; + Type[] genericTypes = + new Type[ + Method.IsGenericMethod ? + Method.GetGenericArguments().Length + : 0 + ]; + for (int i = 0; i < Parameters.Length; i++) { BindParam param = Parameters[i]; + if (param.Kind == BindParamKind.Self) { arguments[i] = instance; @@ -242,6 +258,11 @@ public bool TryGetArguments(object? instance, else if (param.TryConvert(out object? value)) { arguments[i] = value; + + if (param.Type.IsGenericParameter) + { + genericTypes[i] = value?.GetType() ?? typeof(object); + } } else { @@ -249,29 +270,54 @@ public bool TryGetArguments(object? instance, } } + if (Method.IsGenericMethod + && method.ContainsGenericParameters) + { + try + { + // .MakeGenericMethod can throw ArgumentException if + // the type parameters do not obey the constraints. + if (Method is MethodInfo minfo) + { + method = minfo.MakeGenericMethod(genericTypes); + } + } + catch (ArgumentException) + { + } + } + return true; } public void SetArgs(BorrowedReference args, - BorrowedReference kwargs) + BorrowedReference kwargs) { ExtractParameters(args, kwargs, computeDist: false); } public uint SetArgsAndGetDistance(BorrowedReference args, - BorrowedReference kwargs) + BorrowedReference kwargs) { uint distance = 0; if (Method.IsStatic) { - distance += FUNC_MAX_DIST; + distance += FUNC_GROUP_SIZE; } - if (Method.IsGenericMethod) + // NOTE: + // if method contains generic parameters, the distance + // compute logic will take that into consideration. + // but methods can be generic with no generic paramerers, + // e.g. Foo(int, float). + // this ensures these methods are furthur away from + // non-generic instance methods with matching parameters + if (Method.IsGenericMethod + && !Method.ContainsGenericParameters) { - distance += FUNC_MAX_DIST; - distance += (ARG_MAX_DIST * + distance += FUNC_GROUP_SIZE; + distance += (ARG_GROUP_SIZE * (uint)Method.GetGenericArguments().Length); } @@ -289,10 +335,12 @@ uint ExtractParameters(BorrowedReference args, uint distance = 0; uint argidx = 0; - uint argsCount = args != null ? (uint)Runtime.PyTuple_Size(args) : 0; + uint argsCount = + args != null ? (uint)Runtime.PyTuple_Size(args) : 0; bool checkArgs = argsCount > 0; - uint kwargsCount = kwargs != null ? (uint)Runtime.PyDict_Size(kwargs) : 0; + uint kwargsCount = + kwargs != null ? (uint)Runtime.PyDict_Size(kwargs) : 0; bool checkKwargs = kwargsCount > 0; for (uint i = 0; i < Parameters.Length; i++) @@ -322,12 +370,11 @@ uint ExtractParameters(BorrowedReference args, item = Runtime.PyDict_GetItemString(kwargs, slot.Key); if (item != null) { - slot.Value = new PyObject(item); + slot.AssignValue(item, computeDist); if (computeDist) { - distance += - GetTypeDistance(item, slot.Type); + distance += slot.Distance; } continue; @@ -374,14 +421,14 @@ uint ExtractParameters(BorrowedReference args, } } - slot.Value = values; + slot.AssignParamsValue(values); } // if args are already processsed, compute // a default distance for this param slot else if (argidx > argsCount) { - distance += computeDist ? ARG_MAX_DIST : 0; + distance += computeDist ? ARG_GROUP_SIZE : 0; } continue; @@ -396,12 +443,11 @@ uint ExtractParameters(BorrowedReference args, item = Runtime.PyTuple_GetItem(args, (nint)argidx); if (item != null) { - slot.Value = new PyObject(item); + slot.AssignValue(item, computeDist); if (computeDist) { - distance += - GetTypeDistance(item, slot.Type); + distance += slot.Distance; } argidx++; @@ -413,208 +459,6 @@ uint ExtractParameters(BorrowedReference args, return distance; } - - static uint GetTypeDistance(BorrowedReference from, Type to) - { - if (GetCLRType(from, to) is Type argType) - { - return GetTypeDistance(argType, to); - } - - return ARG_MAX_DIST; - } - - static uint GetTypeDistance(Type from, Type to) - { -#if METHODBINDER_SOLVER_NEW_CACHE_DIST - int key = ComputeKey(from, to); - if (s_distMap.TryGetValue(key, out uint dist)) - { - return dist; - } -#endif - - uint distance = 0; - - // NOTE: - // shift distance based on type kind - if (to.IsByRef) - { - distance += TYPE_MAX_DIST; - } - - // NOTE: - // shift distance based on match kind - // exact match - if (from == to) - { - goto computed; - } - - if (from.IsArray != to.IsArray) - { - distance = ARG_MAX_DIST; - goto computed; - } - - // derived match - distance += MATCH_MAX_DIST; - if (to.IsAssignableFrom(from)) - { - distance += GetDerivedTypeDistance(from, to); - goto computed; - } - - // generic match - distance += MATCH_MAX_DIST; - if (to.IsGenericType) - { - goto computed; - } - - // cast/convert match - distance += MATCH_MAX_DIST; - if (TryGetTypePrecedence(from, out uint fromPrec) - && TryGetTypePrecedence(to, out uint toPrec)) - { - distance += GetConvertTypeDistance(fromPrec, toPrec); - goto computed; - } - - distance = ARG_MAX_DIST; - - computed: -#if METHODBINDER_SOLVER_NEW_CACHE_DIST - s_distMap[key] = distance; -#endif - return distance; - } - - // zero when types are equal. - // assumes derived is assignable to @base - // 0 <= x < MATCH_MAX_DIST - static uint GetDerivedTypeDistance(Type derived, Type @base) - { - uint depth = 0; - - Type t = derived; - while (t != null - && t != @base - && depth < MATCH_MAX_DIST) - { - depth++; - t = t.BaseType; - } - - return depth; - } - - // zero when types are equal. - // 0 <= x < MATCH_MAX_DIST - static uint GetConvertTypeDistance(uint from, uint to) - { - return (uint)Math.Abs((int)to - (int)from); - } - - static bool TryGetTypePrecedence(Type of, out uint predecence) - { - predecence = 0; - - if (UNINT == of) - { - predecence = 30; - return true; - } - - if (NINT == of) - { - predecence = 31; - return true; - } - - switch (Type.GetTypeCode(of)) - { - // 0-9 - case TypeCode.Object: - predecence = 1; - return true; - - // 10-19 - case TypeCode.UInt64: - predecence = 10; - return true; - - case TypeCode.UInt32: - predecence = 11; - return true; - - case TypeCode.UInt16: - predecence = 12; - return true; - - case TypeCode.Int64: - predecence = 13; - return true; - - case TypeCode.Int32: - predecence = 14; - return true; - - case TypeCode.Int16: - predecence = 15; - return true; - - case TypeCode.Char: - predecence = 16; - return true; - - case TypeCode.SByte: - predecence = 17; - return true; - - case TypeCode.Byte: - predecence = 18; - return true; - - // 20-29 - - // 30-39 - // UIntPtr | nuint - // IntPtr | nint - - // 40-49 - case TypeCode.Single: - predecence = 40; - return true; - - case TypeCode.Double: - predecence = 41; - return true; - - // 50-59 - case TypeCode.String: - predecence = 50; - return true; - - // 60-69 - case TypeCode.Boolean: - predecence = 60; - return true; - } - - return false; - } - - static int ComputeKey(Type from, Type to) - { - unchecked - { - int hash = 17; - hash = hash * 23 + from.GetHashCode(); - hash = hash * 23 + to.GetHashCode(); - return hash; - } - } } static bool TryBind(MethodBase[] methods, @@ -667,38 +511,34 @@ static bool TryBind(MethodBase[] methods, } } - return TryBindByValue(bindSpecs, args, kwargs, out spec); + return TryBindByValue(index, bindSpecs, args, kwargs, out spec); } // vertical filter - static bool TryBindByValue(BindSpec?[] specs, + static bool TryBindByValue(int count, + BindSpec?[] specs, BorrowedReference args, BorrowedReference kwargs, out BindSpec? spec) { spec = null; - if (specs.Length == 0) + if (count == 0) { return false; } - if (specs.Length > 1 - && specs[0] is BindSpec onlySpec - && specs[1] is null) + if (count == 1) { - spec = onlySpec; + spec = specs[0]; spec!.SetArgs(args, kwargs); return true; } uint closest = uint.MaxValue; - foreach (BindSpec? mspec in specs) + for (int sidx = 0; sidx < count; sidx++) { - if (mspec is null) - { - break; - } + BindSpec mspec = specs[sidx]!; uint distance = mspec!.SetArgsAndGetDistance(args, kwargs); @@ -734,6 +574,39 @@ static bool TryBindByValue(BindSpec?[] specs, { spec = mspec; } + + // NOTE: + // if method has the same distance, and have the least + // optional parameters, lets look at distance computed + // for each parameter and choose method which starts with + // parameters closer to the given args. + // dotnet compiler usually does not allow compiling such + // method calls and calls it ambiguous but we need to make + // a decision eitherway in python. + // e.g. when passing (short, int) or (int, short) + // compiler will complain about ambiguous call between: + // .Foo(short, short) + // .Foo(int, int) + if (spec.Parameters.Length == mspec!.Parameters.Length) + { + int pcount = spec.Parameters.Length; + for (int pidx = 0; pidx < pcount; pidx++) + { + if (pidx < spec.Parameters.Length) + { + BindParam mp = mspec!.Parameters[pidx]; + BindParam sp = spec.Parameters[pidx]; + + if (mp.Distance >= sp.Distance) + { + break; + } + + spec = mspec; + break; + } + } + } } else if (distance < closest) { @@ -820,7 +693,8 @@ static bool TryBindByCount(MethodBase method, // this means method can accept parameters // beyond what is required. expands = - Attribute.IsDefined(mparams[mparams.Length - 1], PAAT); + Attribute.IsDefined(mparams[mparams.Length - 1], + typeof(ParamArrayAttribute)); argSpecs = new BindParam[length]; @@ -920,7 +794,7 @@ static bool TryBindByCount(MethodBase method, return false; matched: - spec = new BindSpec(method, required, optional, expands, argSpecs); + spec = new BindSpec(method, required, optional, argSpecs); return true; } @@ -945,5 +819,253 @@ static HashSet GetKeys(BorrowedReference kwargs) return keys; } + + #region Distance Compute Logic + static Dictionary<(TypeCode, TypeCode), uint> s_builtinDistMap = + new Dictionary<(TypeCode, TypeCode), uint> + { + [(TypeCode.UInt64, TypeCode.UInt64)] = 0, + [(TypeCode.UInt64, TypeCode.Int64)] = 1, + [(TypeCode.UInt64, TypeCode.UInt32)] = 2, + [(TypeCode.UInt64, TypeCode.Int32)] = 3, + [(TypeCode.UInt64, TypeCode.UInt16)] = 4, + [(TypeCode.UInt64, TypeCode.Int16)] = 5, + [(TypeCode.UInt64, TypeCode.Double)] = 6, + [(TypeCode.UInt64, TypeCode.Single)] = 7, + [(TypeCode.UInt64, TypeCode.Decimal)] = 7, + [(TypeCode.UInt64, TypeCode.Object)] = uint.MaxValue - 1, + [(TypeCode.UInt64, TypeCode.DateTime)] = uint.MaxValue, + + [(TypeCode.UInt32, TypeCode.UInt32)] = 0, + [(TypeCode.UInt32, TypeCode.Int32)] = 1, + [(TypeCode.UInt32, TypeCode.UInt16)] = 2, + [(TypeCode.UInt32, TypeCode.Int16)] = 3, + + [(TypeCode.UInt16, TypeCode.UInt16)] = 0, + [(TypeCode.UInt16, TypeCode.Int16)] = 1, + + [(TypeCode.Int64, TypeCode.Int64)] = 0, + [(TypeCode.Int32, TypeCode.Int32)] = 0, + [(TypeCode.Int16, TypeCode.Int16)] = 0, + }; + +#if METHODBINDER_SOLVER_NEW_CACHE_DIST + static readonly Dictionary s_distMap = new(); +#endif + + // https://stackoverflow.com/a/17268854/2350244 + // assumes two groups of parameters, hence *2 + // Foo(params) + static readonly uint MAX_ARGS = + Convert.ToUInt32(Math.Pow(2, 16)) * 2; + + static readonly uint TOTAL_MAX_DIST = uint.MaxValue; + static readonly uint FUNC_GROUP_SIZE = TOTAL_MAX_DIST / 4; + static readonly uint ARG_GROUP_SIZE = FUNC_GROUP_SIZE / MAX_ARGS; + static readonly uint TYPE_GROUP_SIZE = ARG_GROUP_SIZE / 4; + static readonly uint MATCH_GROUP_SIZE = TYPE_GROUP_SIZE / 4; + + public static uint GetTypeDistance(BorrowedReference from, Type to) + { + if (GetCLRType(from) is Type argType) + { + return GetTypeDistance(argType, to); + } + + return ARG_GROUP_SIZE; + } + + static uint GetTypeDistance(Type from, Type to) + { +#if METHODBINDER_SOLVER_NEW_CACHE_DIST + int key = ComputeKey(from, to); + if (s_distMap.TryGetValue(key, out uint dist)) + { + return dist; + } +#endif + + uint distance = 0; + + // NOTE: + // shift distance based on type kind + if (to.IsByRef) + { + distance += TYPE_GROUP_SIZE; + } + + // NOTE: + // shift distance based on match kind + // exact match + if (from == to) + { + goto computed; + } + + if (from.IsArray != to.IsArray) + { + distance = ARG_GROUP_SIZE; + goto computed; + } + + // derived match + distance += MATCH_GROUP_SIZE; + if (to.IsAssignableFrom(from)) + { + distance += GetDerivedTypeDistance(from, to); + goto computed; + } + + // generic match + distance += MATCH_GROUP_SIZE; + if (to.IsGenericParameter) + { + goto computed; + } + + // cast/convert match + distance += MATCH_GROUP_SIZE; + if (TryGetTypePrecedence(from, out uint fromPrec) + && TryGetTypePrecedence(to, out uint toPrec)) + { + distance += GetConvertTypeDistance(fromPrec, toPrec); + goto computed; + } + + distance = ARG_GROUP_SIZE; + + computed: +#if METHODBINDER_SOLVER_NEW_CACHE_DIST + s_distMap[key] = distance; +#endif + return distance; + } + + // zero when types are equal. + // assumes derived is assignable to @base + // 0 <= x < MATCH_MAX_DIST + static uint GetDerivedTypeDistance(Type derived, Type @base) + { + uint depth = 0; + + Type t = derived; + while (t != null + && t != @base + && depth < MATCH_GROUP_SIZE) + { + depth++; + t = t.BaseType; + } + + return depth; + } + + // zero when types are equal. + // 0 <= x < MATCH_MAX_DIST + static uint GetConvertTypeDistance(uint from, uint to) + { + return (uint)Math.Abs((int)to - (int)from); + } + + static bool TryGetTypePrecedence(Type of, out uint predecence) + { + predecence = 0; + + if (typeof(nuint) == of) + { + predecence = 30; + return true; + } + + if (typeof(nint) == of) + { + predecence = 31; + return true; + } + + switch (Type.GetTypeCode(of)) + { + // 0-9 + case TypeCode.Object: + predecence = 1; + return true; + + // 10-19 + case TypeCode.UInt64: + predecence = 10; + return true; + + case TypeCode.UInt32: + predecence = 11; + return true; + + case TypeCode.UInt16: + predecence = 12; + return true; + + case TypeCode.Int64: + predecence = 13; + return true; + + case TypeCode.Int32: + predecence = 14; + return true; + + case TypeCode.Int16: + predecence = 15; + return true; + + case TypeCode.Char: + predecence = 16; + return true; + + case TypeCode.SByte: + predecence = 17; + return true; + + case TypeCode.Byte: + predecence = 18; + return true; + + // 20-29 + + // 30-39 + // UIntPtr | nuint + // IntPtr | nint + + // 40-49 + case TypeCode.Single: + predecence = 40; + return true; + + case TypeCode.Double: + predecence = 41; + return true; + + // 50-59 + case TypeCode.String: + predecence = 50; + return true; + + // 60-69 + case TypeCode.Boolean: + predecence = 60; + return true; + } + + return false; + } + + static int ComputeKey(Type from, Type to) + { + unchecked + { + int hash = 17; + hash = hash * 23 + from.GetHashCode(); + hash = hash * 23 + to.GetHashCode(); + return hash; + } + } + #endregion } } diff --git a/src/runtime/MethodBinder.cs b/src/runtime/MethodBinder.cs index 3f91fec46..626ed6492 100644 --- a/src/runtime/MethodBinder.cs +++ b/src/runtime/MethodBinder.cs @@ -371,14 +371,87 @@ internal static MethodInfo[] MatchParameters(MethodBase[] mi, Type[]? tp) static readonly Type s_voidType = typeof(void); + static bool TryGetManagedValue(BorrowedReference op, out object? value) + { + value = default; + + Type? clrType = GetCLRType(op); + if (clrType == null) + { + return false; + } + + return Converter.ToManaged(op, clrType, out value, true); + } + static bool TryGetManagedValue(BorrowedReference op, Type type, out object? value) { - return TryConvertArgument(op, type, out value, out bool _); + value = default; + + Type? clrType = GetCLRType(op, type); + if (clrType == null) + { + return false; + } + + return Converter.ToManaged(op, clrType, out value, true); } - static Type? GetCLRType(BorrowedReference op, Type type) + static Type? GetCLRType(BorrowedReference op) { - return TryComputeClrArgumentType(type, op); + ManagedType? mt = ManagedType.GetManagedObject(op); + + if (mt is ClassBase b) + { + MaybeType _type = b.type; + return _type.Valid ? _type.Value : null; + } + else if (mt is CLRObject ob) + { + object inst = ob.inst; + if (inst is Type ty) + { + return ty; + } + else + { + return inst?.GetType() ?? typeof(object); + } + } + else + { + if (Runtime.PyType_Check(op)) + { + return Converter.GetTypeByAlias(op); + } + + return Converter.GetTypeByAlias(Runtime.PyObject_TYPE(op), op); + } + } + + static Type? GetCLRType(BorrowedReference op, Type expected) + { + if (expected == typeof(object)) + { + return expected; + } + + BorrowedReference pyType = Runtime.PyObject_TYPE(op); + BorrowedReference pyBuiltinType = Converter.GetPythonTypeByAlias(expected); + + if (pyType != null + && pyBuiltinType == pyType) + { + return expected; + } + + TypeCode typeCode = Type.GetTypeCode(expected); + if (TypeCode.Empty != typeCode) + { + return expected; + } + + return null; } static bool IsOperatorMethod(MethodBase method) => OperatorMethod.IsOperatorMethod(method); @@ -428,7 +501,7 @@ NewReference Invoke(BorrowedReference inst, return Exceptions.RaiseTypeError(msg.ToString()); } - if (TryBind(methods, args, kwargs, allow_redirected, out BindSpec ? spec)) + if (TryBind(methods, args, kwargs, allow_redirected, out BindSpec? spec)) { MethodBase match = spec!.Method; BindParam[] bindParams = spec.Parameters; @@ -471,7 +544,7 @@ NewReference Invoke(BorrowedReference inst, // NOTE: // arg conversion needs to happen before GIL is possibly released - bool converted = spec.TryGetArguments(instance, out object?[] bindArgs); + bool converted = spec.TryGetArguments(instance, out MethodBase method, out object?[] bindArgs); if (!converted) { Exception convertEx = PythonException.FetchCurrent(); @@ -486,11 +559,11 @@ NewReference Invoke(BorrowedReference inst, try { result = - match.Invoke(instance, - invokeAttr: BindingFlags.Default, - binder: null, - parameters: bindArgs, - culture: null); + method.Invoke(instance, + invokeAttr: BindingFlags.Default, + binder: null, + parameters: bindArgs, + culture: null); } catch (Exception e) { diff --git a/src/runtime/Runtime.cs b/src/runtime/Runtime.cs index e21a3432e..2bd2aad3b 100644 --- a/src/runtime/Runtime.cs +++ b/src/runtime/Runtime.cs @@ -72,7 +72,7 @@ private static string GetDefaultDllName(Version version) public static int MainManagedThreadId { get; private set; } - private static readonly List _pyRefs = new (); + private static readonly List _pyRefs = new(); internal static Version PyVersion { @@ -137,7 +137,7 @@ internal static void Initialize(bool initSigs = false) BorrowedReference pyRun = PySys_GetObject(RunSysPropName); if (pyRun != null) { - run = checked((int)PyLong_AsSignedSize_t(pyRun)); + run = checked((int)Delegates.PyLong_AsSignedSize_t(pyRun)); } else { @@ -571,7 +571,7 @@ internal static void CheckExceptionOccurred() if (mt is ClassBase b) { var _type = b.type; - t = _type.Valid ? _type.Value : null; + t = _type.Valid ? _type.Value : null; } else if (mt is CLRObject ob) { @@ -1083,10 +1083,25 @@ internal static NewReference PyLong_FromString(string value, int radix) } + internal static nint? PyLong_AsSignedSize_t(BorrowedReference value) + { + nint result = Delegates.PyLong_AsSignedSize_t(value); + if (result == -1 && Exceptions.ErrorOccurred()) + { + return null; + } + return result; + } - internal static nuint PyLong_AsUnsignedSize_t(BorrowedReference value) => Delegates.PyLong_AsUnsignedSize_t(value); - - internal static nint PyLong_AsSignedSize_t(BorrowedReference value) => Delegates.PyLong_AsSignedSize_t(value); + internal static nuint? PyLong_AsUnsignedSize_t(BorrowedReference value) + { + nuint result = Delegates.PyLong_AsUnsignedSize_t(value); + if (result == unchecked((nuint)(-1)) && Exceptions.ErrorOccurred()) + { + return null; + } + return result; + } internal static long? PyLong_AsLongLong(BorrowedReference value) { @@ -1259,7 +1274,7 @@ internal static bool PyString_CheckExact(BorrowedReference ob) internal static NewReference PyString_FromString(string value) { - fixed(char* ptr = value) + fixed (char* ptr = value) return Delegates.PyUnicode_DecodeUTF16( (IntPtr)ptr, value.Length * sizeof(Char), @@ -1752,7 +1767,7 @@ internal static int PyException_SetTraceback(BorrowedReference ex, BorrowedRefer internal static nint PyGC_Collect() => Delegates.PyGC_Collect(); internal static void Py_CLEAR(BorrowedReference ob, int offset) => ReplaceReference(ob, offset, default); internal static void Py_CLEAR(ref T? ob) - where T: PyObject + where T : PyObject { ob?.Dispose(); ob = null; diff --git a/src/runtime/Types/ArrayObject.cs b/src/runtime/Types/ArrayObject.cs index b95934baf..45f1dc596 100644 --- a/src/runtime/Types/ArrayObject.cs +++ b/src/runtime/Types/ArrayObject.cs @@ -53,13 +53,14 @@ public static NewReference tp_new(BorrowedReference tp, BorrowedReference args, // create single dimensional array if (Runtime.PyInt_Check(op)) { - dimensions[0] = Runtime.PyLong_AsSignedSize_t(op); - if (dimensions[0] == -1 && Exceptions.ErrorOccurred()) + nint? num = Runtime.PyLong_AsSignedSize_t(op); + if (num is null) { Exceptions.Clear(); } else { + dimensions[0] = (long)num!; return NewInstance(arrType.GetElementType(), tp, dimensions); } } @@ -88,12 +89,14 @@ static NewReference CreateMultidimensional(Type elementType, long[] dimensions, return default; } - dimensions[dimIndex] = Runtime.PyLong_AsSignedSize_t(dimObj); - if (dimensions[dimIndex] == -1 && Exceptions.ErrorOccurred()) + nint? num = Runtime.PyLong_AsSignedSize_t(dimObj); + if (num is null) { Exceptions.RaiseTypeError("array constructor expects integer dimensions"); return default; } + + dimensions[dimIndex] = (long)num!; } return NewInstance(elementType, pyType, dimensions); @@ -171,13 +174,15 @@ public static NewReference mp_subscript(BorrowedReference ob, BorrowedReference { return RaiseIndexMustBeIntegerError(idx); } - index = Runtime.PyLong_AsSignedSize_t(idx); - if (index == -1 && Exceptions.ErrorOccurred()) + nint? num = Runtime.PyLong_AsSignedSize_t(idx); + if (num is null) { return Exceptions.RaiseTypeError("invalid index value"); } + index = (long)num!; + if (index < 0) { index = items.LongLength + index; @@ -213,13 +218,14 @@ public static NewReference mp_subscript(BorrowedReference ob, BorrowedReference { return RaiseIndexMustBeIntegerError(op); } - index = Runtime.PyLong_AsSignedSize_t(op); - if (index == -1 && Exceptions.ErrorOccurred()) + nint? num = Runtime.PyLong_AsSignedSize_t(op); + if (num is null) { return Exceptions.RaiseTypeError("invalid index value"); } + index = (long)num!; long len = items.GetLongLength(dimension); if (index < 0) @@ -271,14 +277,16 @@ public static int mp_ass_subscript(BorrowedReference ob, BorrowedReference idx, RaiseIndexMustBeIntegerError(idx); return -1; } - index = Runtime.PyLong_AsSignedSize_t(idx); - if (index == -1 && Exceptions.ErrorOccurred()) + nint? num = Runtime.PyLong_AsSignedSize_t(idx); + if (num is null) { Exceptions.RaiseTypeError("invalid index value"); return -1; } + index = (long)num; + if (index < 0) { index = items.LongLength + index; @@ -311,14 +319,16 @@ public static int mp_ass_subscript(BorrowedReference ob, BorrowedReference idx, RaiseIndexMustBeIntegerError(op); return -1; } - index = Runtime.PyLong_AsSignedSize_t(op); - if (index == -1 && Exceptions.ErrorOccurred()) + nint? num = Runtime.PyLong_AsSignedSize_t(op); + if (num is null) { Exceptions.RaiseTypeError("invalid index value"); return -1; } + index = (long)num!; + long len = items.GetLongLength(dimension); if (index < 0) diff --git a/tests/test_generic.py b/tests/test_generic.py index 3250c2c50..0ff9727be 100644 --- a/tests/test_generic.py +++ b/tests/test_generic.py @@ -29,8 +29,8 @@ def assert_generic_method_by_type(ptype, value, test_type=0): from Python.Test import GenericMethodTest, GenericStaticMethodTest import System - itype = GenericMethodTest[System.Type] - stype = GenericStaticMethodTest[System.Type] + itype = GenericMethodTest[ptype] + stype = GenericStaticMethodTest[ptype] # Explicit selection (static method) result = stype.Overloaded[ptype](value) @@ -66,6 +66,9 @@ def assert_generic_method_by_type(ptype, value, test_type=0): atype = System.Array[ptype] items = atype([value, value, value]) + itype = GenericMethodTest[atype] + stype = GenericStaticMethodTest[atype] + # Explicit selection (static method) result = stype.Overloaded[atype](items) if test_type: @@ -315,51 +318,74 @@ def test_generic_method_type_handling(): from Python.Test import InterfaceTest, ISayHello1, ShortEnum import System - # FIXME: Fails because Converter.GetTypeByAlias returns int32 for any integer, including ulong - # assert_generic_method_by_type(System.UInt64, 18446744073709551615) assert_generic_method_by_type(System.Boolean, True) assert_generic_method_by_type(bool, True) + assert_generic_method_by_type(System.Byte, 255) assert_generic_method_by_type(System.SByte, 127) assert_generic_method_by_type(System.Char, u'A') + assert_generic_method_by_type(System.Int16, 32767) + assert_generic_method_by_type(System.UInt16, 65000) assert_generic_method_by_type(System.Int32, 2147483647) + # https://docs.python.org/3/c-api/long.html#c.PyLong_AsUnsignedLongLong + # 18446744073709551615 is ((unsigned long long)-1) as is reserved for error + assert_generic_method_by_type(System.UInt64, 18446744073709551615 - 1) assert_generic_method_by_type(int, 2147483647) - assert_generic_method_by_type(System.UInt16, 65000) + assert_generic_method_by_type(System.Single, System.Single(3.402823e38)) assert_generic_method_by_type(System.Double, 1.7976931348623157e308) assert_generic_method_by_type(float, 1.7976931348623157e308) + assert_generic_method_by_type(System.Decimal, System.Decimal.One) + assert_generic_method_by_type(System.String, "test") assert_generic_method_by_type(str, "test") + assert_generic_method_by_type(ShortEnum, ShortEnum.Zero) + assert_generic_method_by_type(System.Object, InterfaceTest()) + assert_generic_method_by_type(InterfaceTest, InterfaceTest(), 1) def test_correct_overload_selection(): """Test correct overloading selection for common types.""" - from System import (String, Double, Single, - Int16, Int32, Int64) + from System import (String, Double, Single, Int16, Int32, Int64) from System import Math substr = String("substring") - assert substr.Substring(2) == substr.Substring.__overloads__[Int32]( - Int32(2)) - assert substr.Substring(2, 3) == substr.Substring.__overloads__[Int32, Int32]( - Int32(2), Int32(3)) - - for atype, value1, value2 in zip([Double, Single, Int16, Int32, Int64], - [1.0, 1.0, 1, 1, 1], - [2.0, 0.5, 2, 0, -1]): - assert Math.Abs(atype(value1)) == Math.Abs.__overloads__[atype](atype(value1)) - assert Math.Abs(value1) == Math.Abs.__overloads__[atype](atype(value1)) - assert Math.Max(atype(value1), - atype(value2)) == Math.Max.__overloads__[atype, atype]( - atype(value1), atype(value2)) - assert Math.Max(atype(value1), - value2) == Math.Max.__overloads__[atype, atype]( - atype(value1), atype(value2)) + assert substr.Substring(2) == \ + substr.Substring.__overloads__[Int32](Int32(2)) + + assert substr.Substring(2, 3) == \ + substr.Substring.__overloads__[Int32, Int32](Int32(2), Int32(3)) + + def assert_abs_overload(atype, value1): + assert Math.Abs(atype(value1)) == \ + Math.Abs.__overloads__[atype](atype(value1)) + + assert Math.Abs(value1) == \ + Math.Abs.__overloads__[atype](atype(value1)) + + assert_abs_overload(Double, 1.0) + assert_abs_overload(Single, 1.0) + assert_abs_overload(Int16, 2) + assert_abs_overload(Int32, 1) + assert_abs_overload(Int64, -1) + + def assert_max_overload(atype, value1, value2): + assert Math.Max(atype(value1), atype(value2)) == \ + Math.Max.__overloads__[atype, atype](atype(value1), atype(value2)) + + assert Math.Max(atype(value1), value2) == \ + Math.Max.__overloads__[atype, atype](atype(value1), atype(value2)) + + assert_max_overload(Double, 1.0, 2.0) + assert_max_overload(Single, 1.0, 0.5) + assert_max_overload(Int16, 1, 2) + assert_max_overload(Int32, 1, 0) + assert_max_overload(Int64, 1, -1) clr.AddReference("System.Runtime.InteropServices") from System.Runtime.InteropServices import GCHandle, GCHandleType