From a3c8be7f55287c2d2644a98ebea3637718c5613d Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Mon, 23 Sep 2024 10:45:00 -0400 Subject: [PATCH 1/8] Try comparison with python object if conversion to managed is not possible --- src/runtime/Types/ClassBase.cs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/runtime/Types/ClassBase.cs b/src/runtime/Types/ClassBase.cs index d71897605..8df43efbf 100644 --- a/src/runtime/Types/ClassBase.cs +++ b/src/runtime/Types/ClassBase.cs @@ -149,7 +149,27 @@ public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReferenc case Runtime.Py_GE: co1 = (CLRObject)GetManagedObject(ob)!; co2 = GetManagedObject(other) as CLRObject; - if (co1 == null || co2 == null) + + object co2Inst = null; + // The object comparing against is not a managed object. It could still be a Python object + // that can be compared against (e.g. comparing against a Python string) + if (co2 == null) + { + if (other != null) + { + using var pyCo2 = new PyObject(other); + if (Converter.ToManagedValue(pyCo2, typeof(object), out var result, false)) + { + co2Inst = result; + } + } + } + else + { + co2Inst = co2.inst; + } + + if (co1 == null || co2Inst == null) { return Exceptions.RaiseTypeError("Cannot get managed object"); } @@ -161,7 +181,7 @@ public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReferenc } try { - int cmp = co1Comp.CompareTo(co2.inst); + int cmp = co1Comp.CompareTo(co2Inst); BorrowedReference pyCmp; if (cmp < 0) From 32ab99f78558e79e9de66b83f4f25591eb0547b5 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Mon, 23 Sep 2024 10:46:23 -0400 Subject: [PATCH 2/8] Bump version to 2.0.39 --- src/perf_tests/Python.PerformanceTests.csproj | 70 +- src/runtime/Properties/AssemblyInfo.cs | 16 +- src/runtime/Python.Runtime.csproj | 2 +- src/runtime/Types/ClassBase.cs | 1156 ++++++++--------- 4 files changed, 622 insertions(+), 622 deletions(-) diff --git a/src/perf_tests/Python.PerformanceTests.csproj b/src/perf_tests/Python.PerformanceTests.csproj index 2ef942f0d..dbb269fd2 100644 --- a/src/perf_tests/Python.PerformanceTests.csproj +++ b/src/perf_tests/Python.PerformanceTests.csproj @@ -1,35 +1,35 @@ - - - - net6.0 - false - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - compile - - - - - - - - - - - - - - - - - - + + + + net6.0 + false + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + compile + + + + + + + + + + + + + + + + + + diff --git a/src/runtime/Properties/AssemblyInfo.cs b/src/runtime/Properties/AssemblyInfo.cs index 3b88a6eb5..05f47aff9 100644 --- a/src/runtime/Properties/AssemblyInfo.cs +++ b/src/runtime/Properties/AssemblyInfo.cs @@ -1,8 +1,8 @@ -using System.Reflection; -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Python.EmbeddingTest, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")] -[assembly: InternalsVisibleTo("Python.Test, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")] - -[assembly: AssemblyVersion("2.0.38")] -[assembly: AssemblyFileVersion("2.0.38")] +using System.Reflection; +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Python.EmbeddingTest, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")] +[assembly: InternalsVisibleTo("Python.Test, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")] + +[assembly: AssemblyVersion("2.0.39")] +[assembly: AssemblyFileVersion("2.0.39")] diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index 7c25c9219..c579abaa5 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -5,7 +5,7 @@ Python.Runtime Python.Runtime QuantConnect.pythonnet - 2.0.38 + 2.0.39 false LICENSE https://github.com/pythonnet/pythonnet diff --git a/src/runtime/Types/ClassBase.cs b/src/runtime/Types/ClassBase.cs index 8df43efbf..9bb93ea78 100644 --- a/src/runtime/Types/ClassBase.cs +++ b/src/runtime/Types/ClassBase.cs @@ -1,153 +1,153 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Runtime.Serialization; - -using Python.Runtime.Slots; - -namespace Python.Runtime -{ - /// - /// Base class for Python types that reflect managed types / classes. - /// Concrete subclasses include ClassObject and DelegateObject. This - /// class provides common attributes and common machinery for doing - /// class initialization (initialization of the class __dict__). The - /// concrete subclasses provide slot implementations appropriate for - /// each variety of reflected type. - /// - [Serializable] - internal class ClassBase : ManagedType, IDeserializationCallback - { - [NonSerialized] - internal List dotNetMembers = new(); - internal Indexer? indexer; - internal readonly Dictionary richcompare = new(); - internal MaybeType type; - - internal ClassBase(Type tp) - { - if (tp is null) throw new ArgumentNullException(nameof(type)); - - indexer = null; - type = tp; - } - - internal virtual bool CanSubclass() - { - return !type.Value.IsEnum; - } - - public readonly static Dictionary CilToPyOpMap = new Dictionary - { - ["op_Equality"] = Runtime.Py_EQ, - ["op_Inequality"] = Runtime.Py_NE, - ["op_LessThanOrEqual"] = Runtime.Py_LE, - ["op_GreaterThanOrEqual"] = Runtime.Py_GE, - ["op_LessThan"] = Runtime.Py_LT, - ["op_GreaterThan"] = Runtime.Py_GT, - }; - - /// - /// Default implementation of [] semantics for reflected types. - /// - public virtual NewReference type_subscript(BorrowedReference idx) - { - Type[]? types = Runtime.PythonArgsToTypeArray(idx); - if (types == null) - { - return Exceptions.RaiseTypeError("type(s) expected"); - } - - if (!type.Valid) - { - return Exceptions.RaiseTypeError(type.DeletedMessage); - } - - Type? target = GenericUtil.GenericForType(type.Value, types.Length); - - if (target != null) - { - Type t; - try - { - // MakeGenericType can throw ArgumentException - t = target.MakeGenericType(types); - } - catch (ArgumentException e) - { - return Exceptions.RaiseTypeError(e.Message); - } - var c = ClassManager.GetClass(t); - return new NewReference(c); - } - - return Exceptions.RaiseTypeError($"{type.Value.Namespace}.{type.Name} does not accept {types.Length} generic parameters"); - } - - /// - /// Standard comparison implementation for instances of reflected types. - /// - public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReference other, int op) - { - CLRObject co1; - CLRObject? co2; - BorrowedReference tp = Runtime.PyObject_TYPE(ob); - var cls = (ClassBase)GetManagedObject(tp)!; - // C# operator methods take precedence over IComparable. - // We first check if there's a comparison operator by looking up the richcompare table, - // otherwise fallback to checking if an IComparable interface is handled. - if (cls.richcompare.TryGetValue(op, out var methodObject)) - { - // Wrap the `other` argument of a binary comparison operator in a PyTuple. - using var args = Runtime.PyTuple_New(1); - Runtime.PyTuple_SetItem(args.Borrow(), 0, other); - return methodObject.Invoke(ob, args.Borrow(), null); - } - - switch (op) - { - case Runtime.Py_EQ: - case Runtime.Py_NE: - BorrowedReference pytrue = Runtime.PyTrue; - BorrowedReference pyfalse = Runtime.PyFalse; - - // swap true and false for NE - if (op != Runtime.Py_EQ) - { - pytrue = Runtime.PyFalse; - pyfalse = Runtime.PyTrue; - } - - if (ob == other) - { - return new NewReference(pytrue); - } - - co1 = (CLRObject)GetManagedObject(ob)!; - co2 = GetManagedObject(other) as CLRObject; - if (null == co2) - { - return new NewReference(pyfalse); - } - - object o1 = co1.inst; - object o2 = co2.inst; - - if (Equals(o1, o2)) - { - return new NewReference(pytrue); - } - - return new NewReference(pyfalse); - case Runtime.Py_LT: - case Runtime.Py_LE: - case Runtime.Py_GT: - case Runtime.Py_GE: - co1 = (CLRObject)GetManagedObject(ob)!; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; + +using Python.Runtime.Slots; + +namespace Python.Runtime +{ + /// + /// Base class for Python types that reflect managed types / classes. + /// Concrete subclasses include ClassObject and DelegateObject. This + /// class provides common attributes and common machinery for doing + /// class initialization (initialization of the class __dict__). The + /// concrete subclasses provide slot implementations appropriate for + /// each variety of reflected type. + /// + [Serializable] + internal class ClassBase : ManagedType, IDeserializationCallback + { + [NonSerialized] + internal List dotNetMembers = new(); + internal Indexer? indexer; + internal readonly Dictionary richcompare = new(); + internal MaybeType type; + + internal ClassBase(Type tp) + { + if (tp is null) throw new ArgumentNullException(nameof(type)); + + indexer = null; + type = tp; + } + + internal virtual bool CanSubclass() + { + return !type.Value.IsEnum; + } + + public readonly static Dictionary CilToPyOpMap = new Dictionary + { + ["op_Equality"] = Runtime.Py_EQ, + ["op_Inequality"] = Runtime.Py_NE, + ["op_LessThanOrEqual"] = Runtime.Py_LE, + ["op_GreaterThanOrEqual"] = Runtime.Py_GE, + ["op_LessThan"] = Runtime.Py_LT, + ["op_GreaterThan"] = Runtime.Py_GT, + }; + + /// + /// Default implementation of [] semantics for reflected types. + /// + public virtual NewReference type_subscript(BorrowedReference idx) + { + Type[]? types = Runtime.PythonArgsToTypeArray(idx); + if (types == null) + { + return Exceptions.RaiseTypeError("type(s) expected"); + } + + if (!type.Valid) + { + return Exceptions.RaiseTypeError(type.DeletedMessage); + } + + Type? target = GenericUtil.GenericForType(type.Value, types.Length); + + if (target != null) + { + Type t; + try + { + // MakeGenericType can throw ArgumentException + t = target.MakeGenericType(types); + } + catch (ArgumentException e) + { + return Exceptions.RaiseTypeError(e.Message); + } + var c = ClassManager.GetClass(t); + return new NewReference(c); + } + + return Exceptions.RaiseTypeError($"{type.Value.Namespace}.{type.Name} does not accept {types.Length} generic parameters"); + } + + /// + /// Standard comparison implementation for instances of reflected types. + /// + public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReference other, int op) + { + CLRObject co1; + CLRObject? co2; + BorrowedReference tp = Runtime.PyObject_TYPE(ob); + var cls = (ClassBase)GetManagedObject(tp)!; + // C# operator methods take precedence over IComparable. + // We first check if there's a comparison operator by looking up the richcompare table, + // otherwise fallback to checking if an IComparable interface is handled. + if (cls.richcompare.TryGetValue(op, out var methodObject)) + { + // Wrap the `other` argument of a binary comparison operator in a PyTuple. + using var args = Runtime.PyTuple_New(1); + Runtime.PyTuple_SetItem(args.Borrow(), 0, other); + return methodObject.Invoke(ob, args.Borrow(), null); + } + + switch (op) + { + case Runtime.Py_EQ: + case Runtime.Py_NE: + BorrowedReference pytrue = Runtime.PyTrue; + BorrowedReference pyfalse = Runtime.PyFalse; + + // swap true and false for NE + if (op != Runtime.Py_EQ) + { + pytrue = Runtime.PyFalse; + pyfalse = Runtime.PyTrue; + } + + if (ob == other) + { + return new NewReference(pytrue); + } + + co1 = (CLRObject)GetManagedObject(ob)!; + co2 = GetManagedObject(other) as CLRObject; + if (null == co2) + { + return new NewReference(pyfalse); + } + + object o1 = co1.inst; + object o2 = co2.inst; + + if (Equals(o1, o2)) + { + return new NewReference(pytrue); + } + + return new NewReference(pyfalse); + case Runtime.Py_LT: + case Runtime.Py_LE: + case Runtime.Py_GT: + case Runtime.Py_GE: + co1 = (CLRObject)GetManagedObject(ob)!; co2 = GetManagedObject(other) as CLRObject; object co2Inst = null; @@ -168,431 +168,431 @@ public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReferenc { co2Inst = co2.inst; } - - if (co1 == null || co2Inst == null) - { - return Exceptions.RaiseTypeError("Cannot get managed object"); - } - var co1Comp = co1.inst as IComparable; - if (co1Comp == null) - { - Type co1Type = co1.GetType(); - return Exceptions.RaiseTypeError($"Cannot convert object of type {co1Type} to IComparable"); - } - try - { - int cmp = co1Comp.CompareTo(co2Inst); - - BorrowedReference pyCmp; - if (cmp < 0) - { - if (op == Runtime.Py_LT || op == Runtime.Py_LE) - { - pyCmp = Runtime.PyTrue; - } - else - { - pyCmp = Runtime.PyFalse; - } - } - else if (cmp == 0) - { - if (op == Runtime.Py_LE || op == Runtime.Py_GE) - { - pyCmp = Runtime.PyTrue; - } - else - { - pyCmp = Runtime.PyFalse; - } - } - else - { - if (op == Runtime.Py_GE || op == Runtime.Py_GT) - { - pyCmp = Runtime.PyTrue; - } - else - { - pyCmp = Runtime.PyFalse; - } - } - return new NewReference(pyCmp); - } - catch (ArgumentException e) - { - return Exceptions.RaiseTypeError(e.Message); - } - default: - return new NewReference(Runtime.PyNotImplemented); - } - } - - /// - /// Standard iteration support for instances of reflected types. This - /// allows natural iteration over objects that either are IEnumerable - /// or themselves support IEnumerator directly. - /// - static NewReference tp_iter_impl(BorrowedReference ob) - { - var co = GetManagedObject(ob) as CLRObject; - if (co == null) - { - return Exceptions.RaiseTypeError("invalid object"); - } - - var e = co.inst as IEnumerable; - IEnumerator? o; - if (e != null) - { - o = e.GetEnumerator(); - } - else - { - o = co.inst as IEnumerator; - - if (o == null) - { - return Exceptions.RaiseTypeError("iteration over non-sequence"); - } - } - - var elemType = typeof(object); - var iterType = co.inst.GetType(); - foreach(var ifc in iterType.GetInterfaces()) - { - if (ifc.IsGenericType) - { - var genTypeDef = ifc.GetGenericTypeDefinition(); - if (genTypeDef == typeof(IEnumerable<>) || genTypeDef == typeof(IEnumerator<>)) - { - elemType = ifc.GetGenericArguments()[0]; - break; - } - } - } - - return new Iterator(o, elemType).Alloc(); - } - - - /// - /// Standard __hash__ implementation for instances of reflected types. - /// - public static nint tp_hash(BorrowedReference ob) - { - var co = GetManagedObject(ob) as CLRObject; - if (co == null) - { - Exceptions.RaiseTypeError("unhashable type"); - return 0; - } - return co.inst.GetHashCode(); - } - - - /// - /// Standard __str__ implementation for instances of reflected types. - /// - public static NewReference tp_str(BorrowedReference ob) - { - var co = GetManagedObject(ob) as CLRObject; - if (co == null) - { - return Exceptions.RaiseTypeError("invalid object"); - } - try - { - return Runtime.PyString_FromString(co.inst.ToString()); - } - catch (Exception e) - { - if (e.InnerException != null) - { - e = e.InnerException; - } - Exceptions.SetError(e); - return default; - } - } - - public static NewReference tp_repr(BorrowedReference ob) - { - var co = GetManagedObject(ob) as CLRObject; - if (co == null) - { - return Exceptions.RaiseTypeError("invalid object"); - } - try - { - //if __repr__ is defined, use it - var instType = co.inst.GetType(); - System.Reflection.MethodInfo methodInfo = instType.GetMethod("__repr__"); - if (methodInfo != null && methodInfo.IsPublic) - { - var reprString = methodInfo.Invoke(co.inst, null) as string; - return reprString is null ? new NewReference(Runtime.PyNone) : Runtime.PyString_FromString(reprString); - } - - //otherwise use the standard object.__repr__(inst) - using var args = Runtime.PyTuple_New(1); - Runtime.PyTuple_SetItem(args.Borrow(), 0, ob); - using var reprFunc = Runtime.PyObject_GetAttr(Runtime.PyBaseObjectType, PyIdentifier.__repr__); - return Runtime.PyObject_Call(reprFunc.Borrow(), args.Borrow(), null); - } - catch (Exception e) - { - if (e.InnerException != null) - { - e = e.InnerException; - } - Exceptions.SetError(e); - return default; - } - } - - - /// - /// Standard dealloc implementation for instances of reflected types. - /// - public static void tp_dealloc(NewReference lastRef) - { - Runtime.PyObject_GC_UnTrack(lastRef.Borrow()); - - CallClear(lastRef.Borrow()); - - DecrefTypeAndFree(lastRef.Steal()); - } - - public static int tp_clear(BorrowedReference ob) - { - var weakrefs = Runtime.PyObject_GetWeakRefList(ob); - if (weakrefs != null) - { - Runtime.PyObject_ClearWeakRefs(ob); - } - - TryFreeGCHandle(ob); - - int baseClearResult = BaseUnmanagedClear(ob); - if (baseClearResult != 0) - { - return baseClearResult; - } - - ClearObjectDict(ob); - return 0; - } - - internal static unsafe int BaseUnmanagedClear(BorrowedReference ob) - { - var type = Runtime.PyObject_TYPE(ob); - var unmanagedBase = GetUnmanagedBaseType(type); - var clearPtr = Util.ReadIntPtr(unmanagedBase, TypeOffset.tp_clear); - if (clearPtr == IntPtr.Zero) - { - return 0; - } - var clear = (delegate* unmanaged[Cdecl])clearPtr; - - bool usesSubtypeClear = clearPtr == TypeManager.subtype_clear; - if (usesSubtypeClear) - { - // workaround for https://bugs.python.org/issue45266 (subtype_clear) - using var dict = Runtime.PyObject_GenericGetDict(ob); - if (Runtime.PyMapping_HasKey(dict.Borrow(), PyIdentifier.__clear_reentry_guard__) != 0) - return 0; - int res = Runtime.PyDict_SetItem(dict.Borrow(), PyIdentifier.__clear_reentry_guard__, Runtime.None); - if (res != 0) return res; - - res = clear(ob); - Runtime.PyDict_DelItem(dict.Borrow(), PyIdentifier.__clear_reentry_guard__); - return res; - } - return clear(ob); - } - - protected override Dictionary OnSave(BorrowedReference ob) - { - var context = base.OnSave(ob) ?? new(); - context["impl"] = this; - return context; - } - - protected override void OnLoad(BorrowedReference ob, Dictionary? context) - { - base.OnLoad(ob, context); - var gcHandle = GCHandle.Alloc(this); - SetGCHandle(ob, gcHandle); - } - - - /// - /// Implements __getitem__ for reflected classes and value types. - /// - static NewReference mp_subscript_impl(BorrowedReference ob, BorrowedReference idx) - { - BorrowedReference tp = Runtime.PyObject_TYPE(ob); - var cls = (ClassBase)GetManagedObject(tp)!; - - if (cls.indexer == null || !cls.indexer.CanGet) - { - Exceptions.SetError(Exceptions.TypeError, "unindexable object"); - return default; - } - - // Arg may be a tuple in the case of an indexer with multiple - // parameters. If so, use it directly, else make a new tuple - // with the index arg (method binders expect arg tuples). - if (!Runtime.PyTuple_Check(idx)) - { - using var argTuple = Runtime.PyTuple_New(1); - Runtime.PyTuple_SetItem(argTuple.Borrow(), 0, idx); - return cls.indexer.GetItem(ob, argTuple.Borrow()); - } - else - { - return cls.indexer.GetItem(ob, idx); - } - } - - - /// - /// Implements __setitem__ for reflected classes and value types. - /// - static int mp_ass_subscript_impl(BorrowedReference ob, BorrowedReference idx, BorrowedReference v) - { - BorrowedReference tp = Runtime.PyObject_TYPE(ob); - var cls = (ClassBase)GetManagedObject(tp)!; - - if (cls.indexer == null || !cls.indexer.CanSet) - { - Exceptions.SetError(Exceptions.TypeError, "object doesn't support item assignment"); - return -1; - } - - // Arg may be a tuple in the case of an indexer with multiple - // parameters. If so, use it directly, else make a new tuple - // with the index arg (method binders expect arg tuples). - NewReference argsTuple = default; - - if (!Runtime.PyTuple_Check(idx)) - { - argsTuple = Runtime.PyTuple_New(1); - Runtime.PyTuple_SetItem(argsTuple.Borrow(), 0, idx); - idx = argsTuple.Borrow(); - } - - // Get the args passed in. - var i = Runtime.PyTuple_Size(idx); - using var defaultArgs = cls.indexer.GetDefaultArgs(idx); - var numOfDefaultArgs = Runtime.PyTuple_Size(defaultArgs.Borrow()); - var temp = i + numOfDefaultArgs; - using var real = Runtime.PyTuple_New(temp + 1); - for (var n = 0; n < i; n++) - { - BorrowedReference item = Runtime.PyTuple_GetItem(idx, n); - Runtime.PyTuple_SetItem(real.Borrow(), n, item); - } - - argsTuple.Dispose(); - - // Add Default Args if needed - for (var n = 0; n < numOfDefaultArgs; n++) - { - BorrowedReference item = Runtime.PyTuple_GetItem(defaultArgs.Borrow(), n); - Runtime.PyTuple_SetItem(real.Borrow(), n + i, item); - } - i = temp; - - // Add value to argument list - Runtime.PyTuple_SetItem(real.Borrow(), i, v); - - cls.indexer.SetItem(ob, real.Borrow()); - - if (Exceptions.ErrorOccurred()) - { - return -1; - } - - return 0; - } - - static NewReference tp_call_impl(BorrowedReference ob, BorrowedReference args, BorrowedReference kw) - { - BorrowedReference tp = Runtime.PyObject_TYPE(ob); - var self = (ClassBase)GetManagedObject(tp)!; - - if (!self.type.Valid) - { - return Exceptions.RaiseTypeError(self.type.DeletedMessage); - } - - Type type = self.type.Value; - - var calls = GetCallImplementations(type).ToList(); - Debug.Assert(calls.Count > 0); - var callBinder = new MethodBinder(); - foreach (MethodInfo call in calls) - { - callBinder.AddMethod(call, true); - } - return callBinder.Invoke(ob, args, kw); - } - - static IEnumerable GetCallImplementations(Type type) - => type.GetMethods(BindingFlags.Public | BindingFlags.Instance) - .Where(m => m.Name == "__call__"); - - public virtual void InitializeSlots(BorrowedReference pyType, SlotsHolder slotsHolder) - { - if (!this.type.Valid) return; - - if (GetCallImplementations(this.type.Value).Any()) - { - TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.tp_call, new Interop.BBB_N(tp_call_impl), slotsHolder); - } - - if (indexer is not null) - { - if (indexer.CanGet) - { - TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.mp_subscript, new Interop.BB_N(mp_subscript_impl), slotsHolder); - } - if (indexer.CanSet) - { - TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.mp_ass_subscript, new Interop.BBB_I32(mp_ass_subscript_impl), slotsHolder); - } - } - - if (typeof(IEnumerable).IsAssignableFrom(type.Value) - || typeof(IEnumerator).IsAssignableFrom(type.Value)) - { - TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.tp_iter, new Interop.B_N(tp_iter_impl), slotsHolder); - } - - if (MpLengthSlot.CanAssign(type.Value)) - { - TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.mp_length, new Interop.B_P(MpLengthSlot.impl), slotsHolder); - } - } - - public virtual bool HasCustomNew() => this.GetType().GetMethod("tp_new") is not null; - - public override bool Init(BorrowedReference obj, BorrowedReference args, BorrowedReference kw) - { - if (this.HasCustomNew()) - // initialization must be done in tp_new - return true; - - return base.Init(obj, args, kw); - } - - protected virtual void OnDeserialization(object sender) - { - this.dotNetMembers = new List(); - } - - void IDeserializationCallback.OnDeserialization(object sender) => this.OnDeserialization(sender); - } -} + + if (co1 == null || co2Inst == null) + { + return Exceptions.RaiseTypeError("Cannot get managed object"); + } + var co1Comp = co1.inst as IComparable; + if (co1Comp == null) + { + Type co1Type = co1.GetType(); + return Exceptions.RaiseTypeError($"Cannot convert object of type {co1Type} to IComparable"); + } + try + { + int cmp = co1Comp.CompareTo(co2Inst); + + BorrowedReference pyCmp; + if (cmp < 0) + { + if (op == Runtime.Py_LT || op == Runtime.Py_LE) + { + pyCmp = Runtime.PyTrue; + } + else + { + pyCmp = Runtime.PyFalse; + } + } + else if (cmp == 0) + { + if (op == Runtime.Py_LE || op == Runtime.Py_GE) + { + pyCmp = Runtime.PyTrue; + } + else + { + pyCmp = Runtime.PyFalse; + } + } + else + { + if (op == Runtime.Py_GE || op == Runtime.Py_GT) + { + pyCmp = Runtime.PyTrue; + } + else + { + pyCmp = Runtime.PyFalse; + } + } + return new NewReference(pyCmp); + } + catch (ArgumentException e) + { + return Exceptions.RaiseTypeError(e.Message); + } + default: + return new NewReference(Runtime.PyNotImplemented); + } + } + + /// + /// Standard iteration support for instances of reflected types. This + /// allows natural iteration over objects that either are IEnumerable + /// or themselves support IEnumerator directly. + /// + static NewReference tp_iter_impl(BorrowedReference ob) + { + var co = GetManagedObject(ob) as CLRObject; + if (co == null) + { + return Exceptions.RaiseTypeError("invalid object"); + } + + var e = co.inst as IEnumerable; + IEnumerator? o; + if (e != null) + { + o = e.GetEnumerator(); + } + else + { + o = co.inst as IEnumerator; + + if (o == null) + { + return Exceptions.RaiseTypeError("iteration over non-sequence"); + } + } + + var elemType = typeof(object); + var iterType = co.inst.GetType(); + foreach(var ifc in iterType.GetInterfaces()) + { + if (ifc.IsGenericType) + { + var genTypeDef = ifc.GetGenericTypeDefinition(); + if (genTypeDef == typeof(IEnumerable<>) || genTypeDef == typeof(IEnumerator<>)) + { + elemType = ifc.GetGenericArguments()[0]; + break; + } + } + } + + return new Iterator(o, elemType).Alloc(); + } + + + /// + /// Standard __hash__ implementation for instances of reflected types. + /// + public static nint tp_hash(BorrowedReference ob) + { + var co = GetManagedObject(ob) as CLRObject; + if (co == null) + { + Exceptions.RaiseTypeError("unhashable type"); + return 0; + } + return co.inst.GetHashCode(); + } + + + /// + /// Standard __str__ implementation for instances of reflected types. + /// + public static NewReference tp_str(BorrowedReference ob) + { + var co = GetManagedObject(ob) as CLRObject; + if (co == null) + { + return Exceptions.RaiseTypeError("invalid object"); + } + try + { + return Runtime.PyString_FromString(co.inst.ToString()); + } + catch (Exception e) + { + if (e.InnerException != null) + { + e = e.InnerException; + } + Exceptions.SetError(e); + return default; + } + } + + public static NewReference tp_repr(BorrowedReference ob) + { + var co = GetManagedObject(ob) as CLRObject; + if (co == null) + { + return Exceptions.RaiseTypeError("invalid object"); + } + try + { + //if __repr__ is defined, use it + var instType = co.inst.GetType(); + System.Reflection.MethodInfo methodInfo = instType.GetMethod("__repr__"); + if (methodInfo != null && methodInfo.IsPublic) + { + var reprString = methodInfo.Invoke(co.inst, null) as string; + return reprString is null ? new NewReference(Runtime.PyNone) : Runtime.PyString_FromString(reprString); + } + + //otherwise use the standard object.__repr__(inst) + using var args = Runtime.PyTuple_New(1); + Runtime.PyTuple_SetItem(args.Borrow(), 0, ob); + using var reprFunc = Runtime.PyObject_GetAttr(Runtime.PyBaseObjectType, PyIdentifier.__repr__); + return Runtime.PyObject_Call(reprFunc.Borrow(), args.Borrow(), null); + } + catch (Exception e) + { + if (e.InnerException != null) + { + e = e.InnerException; + } + Exceptions.SetError(e); + return default; + } + } + + + /// + /// Standard dealloc implementation for instances of reflected types. + /// + public static void tp_dealloc(NewReference lastRef) + { + Runtime.PyObject_GC_UnTrack(lastRef.Borrow()); + + CallClear(lastRef.Borrow()); + + DecrefTypeAndFree(lastRef.Steal()); + } + + public static int tp_clear(BorrowedReference ob) + { + var weakrefs = Runtime.PyObject_GetWeakRefList(ob); + if (weakrefs != null) + { + Runtime.PyObject_ClearWeakRefs(ob); + } + + TryFreeGCHandle(ob); + + int baseClearResult = BaseUnmanagedClear(ob); + if (baseClearResult != 0) + { + return baseClearResult; + } + + ClearObjectDict(ob); + return 0; + } + + internal static unsafe int BaseUnmanagedClear(BorrowedReference ob) + { + var type = Runtime.PyObject_TYPE(ob); + var unmanagedBase = GetUnmanagedBaseType(type); + var clearPtr = Util.ReadIntPtr(unmanagedBase, TypeOffset.tp_clear); + if (clearPtr == IntPtr.Zero) + { + return 0; + } + var clear = (delegate* unmanaged[Cdecl])clearPtr; + + bool usesSubtypeClear = clearPtr == TypeManager.subtype_clear; + if (usesSubtypeClear) + { + // workaround for https://bugs.python.org/issue45266 (subtype_clear) + using var dict = Runtime.PyObject_GenericGetDict(ob); + if (Runtime.PyMapping_HasKey(dict.Borrow(), PyIdentifier.__clear_reentry_guard__) != 0) + return 0; + int res = Runtime.PyDict_SetItem(dict.Borrow(), PyIdentifier.__clear_reentry_guard__, Runtime.None); + if (res != 0) return res; + + res = clear(ob); + Runtime.PyDict_DelItem(dict.Borrow(), PyIdentifier.__clear_reentry_guard__); + return res; + } + return clear(ob); + } + + protected override Dictionary OnSave(BorrowedReference ob) + { + var context = base.OnSave(ob) ?? new(); + context["impl"] = this; + return context; + } + + protected override void OnLoad(BorrowedReference ob, Dictionary? context) + { + base.OnLoad(ob, context); + var gcHandle = GCHandle.Alloc(this); + SetGCHandle(ob, gcHandle); + } + + + /// + /// Implements __getitem__ for reflected classes and value types. + /// + static NewReference mp_subscript_impl(BorrowedReference ob, BorrowedReference idx) + { + BorrowedReference tp = Runtime.PyObject_TYPE(ob); + var cls = (ClassBase)GetManagedObject(tp)!; + + if (cls.indexer == null || !cls.indexer.CanGet) + { + Exceptions.SetError(Exceptions.TypeError, "unindexable object"); + return default; + } + + // Arg may be a tuple in the case of an indexer with multiple + // parameters. If so, use it directly, else make a new tuple + // with the index arg (method binders expect arg tuples). + if (!Runtime.PyTuple_Check(idx)) + { + using var argTuple = Runtime.PyTuple_New(1); + Runtime.PyTuple_SetItem(argTuple.Borrow(), 0, idx); + return cls.indexer.GetItem(ob, argTuple.Borrow()); + } + else + { + return cls.indexer.GetItem(ob, idx); + } + } + + + /// + /// Implements __setitem__ for reflected classes and value types. + /// + static int mp_ass_subscript_impl(BorrowedReference ob, BorrowedReference idx, BorrowedReference v) + { + BorrowedReference tp = Runtime.PyObject_TYPE(ob); + var cls = (ClassBase)GetManagedObject(tp)!; + + if (cls.indexer == null || !cls.indexer.CanSet) + { + Exceptions.SetError(Exceptions.TypeError, "object doesn't support item assignment"); + return -1; + } + + // Arg may be a tuple in the case of an indexer with multiple + // parameters. If so, use it directly, else make a new tuple + // with the index arg (method binders expect arg tuples). + NewReference argsTuple = default; + + if (!Runtime.PyTuple_Check(idx)) + { + argsTuple = Runtime.PyTuple_New(1); + Runtime.PyTuple_SetItem(argsTuple.Borrow(), 0, idx); + idx = argsTuple.Borrow(); + } + + // Get the args passed in. + var i = Runtime.PyTuple_Size(idx); + using var defaultArgs = cls.indexer.GetDefaultArgs(idx); + var numOfDefaultArgs = Runtime.PyTuple_Size(defaultArgs.Borrow()); + var temp = i + numOfDefaultArgs; + using var real = Runtime.PyTuple_New(temp + 1); + for (var n = 0; n < i; n++) + { + BorrowedReference item = Runtime.PyTuple_GetItem(idx, n); + Runtime.PyTuple_SetItem(real.Borrow(), n, item); + } + + argsTuple.Dispose(); + + // Add Default Args if needed + for (var n = 0; n < numOfDefaultArgs; n++) + { + BorrowedReference item = Runtime.PyTuple_GetItem(defaultArgs.Borrow(), n); + Runtime.PyTuple_SetItem(real.Borrow(), n + i, item); + } + i = temp; + + // Add value to argument list + Runtime.PyTuple_SetItem(real.Borrow(), i, v); + + cls.indexer.SetItem(ob, real.Borrow()); + + if (Exceptions.ErrorOccurred()) + { + return -1; + } + + return 0; + } + + static NewReference tp_call_impl(BorrowedReference ob, BorrowedReference args, BorrowedReference kw) + { + BorrowedReference tp = Runtime.PyObject_TYPE(ob); + var self = (ClassBase)GetManagedObject(tp)!; + + if (!self.type.Valid) + { + return Exceptions.RaiseTypeError(self.type.DeletedMessage); + } + + Type type = self.type.Value; + + var calls = GetCallImplementations(type).ToList(); + Debug.Assert(calls.Count > 0); + var callBinder = new MethodBinder(); + foreach (MethodInfo call in calls) + { + callBinder.AddMethod(call, true); + } + return callBinder.Invoke(ob, args, kw); + } + + static IEnumerable GetCallImplementations(Type type) + => type.GetMethods(BindingFlags.Public | BindingFlags.Instance) + .Where(m => m.Name == "__call__"); + + public virtual void InitializeSlots(BorrowedReference pyType, SlotsHolder slotsHolder) + { + if (!this.type.Valid) return; + + if (GetCallImplementations(this.type.Value).Any()) + { + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.tp_call, new Interop.BBB_N(tp_call_impl), slotsHolder); + } + + if (indexer is not null) + { + if (indexer.CanGet) + { + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.mp_subscript, new Interop.BB_N(mp_subscript_impl), slotsHolder); + } + if (indexer.CanSet) + { + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.mp_ass_subscript, new Interop.BBB_I32(mp_ass_subscript_impl), slotsHolder); + } + } + + if (typeof(IEnumerable).IsAssignableFrom(type.Value) + || typeof(IEnumerator).IsAssignableFrom(type.Value)) + { + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.tp_iter, new Interop.B_N(tp_iter_impl), slotsHolder); + } + + if (MpLengthSlot.CanAssign(type.Value)) + { + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.mp_length, new Interop.B_P(MpLengthSlot.impl), slotsHolder); + } + } + + public virtual bool HasCustomNew() => this.GetType().GetMethod("tp_new") is not null; + + public override bool Init(BorrowedReference obj, BorrowedReference args, BorrowedReference kw) + { + if (this.HasCustomNew()) + // initialization must be done in tp_new + return true; + + return base.Init(obj, args, kw); + } + + protected virtual void OnDeserialization(object sender) + { + this.dotNetMembers = new List(); + } + + void IDeserializationCallback.OnDeserialization(object sender) => this.OnDeserialization(sender); + } +} From 2de0a8587cc81ac3d3c0431cdffa92dd91d05638 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Mon, 23 Sep 2024 10:56:01 -0400 Subject: [PATCH 3/8] Cleanup --- src/perf_tests/Python.PerformanceTests.csproj | 70 +- src/runtime/Properties/AssemblyInfo.cs | 16 +- src/runtime/Types/ClassBase.cs | 1156 ++++++++--------- 3 files changed, 621 insertions(+), 621 deletions(-) diff --git a/src/perf_tests/Python.PerformanceTests.csproj b/src/perf_tests/Python.PerformanceTests.csproj index dbb269fd2..b437fe532 100644 --- a/src/perf_tests/Python.PerformanceTests.csproj +++ b/src/perf_tests/Python.PerformanceTests.csproj @@ -1,35 +1,35 @@ - - - - net6.0 - false - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - compile - - - - - - - - - - - - - - - - - - + + + + net6.0 + false + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + compile + + + + + + + + + + + + + + + + + + diff --git a/src/runtime/Properties/AssemblyInfo.cs b/src/runtime/Properties/AssemblyInfo.cs index 05f47aff9..ffb1308a4 100644 --- a/src/runtime/Properties/AssemblyInfo.cs +++ b/src/runtime/Properties/AssemblyInfo.cs @@ -1,8 +1,8 @@ -using System.Reflection; -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Python.EmbeddingTest, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")] -[assembly: InternalsVisibleTo("Python.Test, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")] - -[assembly: AssemblyVersion("2.0.39")] -[assembly: AssemblyFileVersion("2.0.39")] +using System.Reflection; +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Python.EmbeddingTest, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")] +[assembly: InternalsVisibleTo("Python.Test, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")] + +[assembly: AssemblyVersion("2.0.39")] +[assembly: AssemblyFileVersion("2.0.39")] diff --git a/src/runtime/Types/ClassBase.cs b/src/runtime/Types/ClassBase.cs index 9bb93ea78..8df43efbf 100644 --- a/src/runtime/Types/ClassBase.cs +++ b/src/runtime/Types/ClassBase.cs @@ -1,153 +1,153 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Runtime.Serialization; - -using Python.Runtime.Slots; - -namespace Python.Runtime -{ - /// - /// Base class for Python types that reflect managed types / classes. - /// Concrete subclasses include ClassObject and DelegateObject. This - /// class provides common attributes and common machinery for doing - /// class initialization (initialization of the class __dict__). The - /// concrete subclasses provide slot implementations appropriate for - /// each variety of reflected type. - /// - [Serializable] - internal class ClassBase : ManagedType, IDeserializationCallback - { - [NonSerialized] - internal List dotNetMembers = new(); - internal Indexer? indexer; - internal readonly Dictionary richcompare = new(); - internal MaybeType type; - - internal ClassBase(Type tp) - { - if (tp is null) throw new ArgumentNullException(nameof(type)); - - indexer = null; - type = tp; - } - - internal virtual bool CanSubclass() - { - return !type.Value.IsEnum; - } - - public readonly static Dictionary CilToPyOpMap = new Dictionary - { - ["op_Equality"] = Runtime.Py_EQ, - ["op_Inequality"] = Runtime.Py_NE, - ["op_LessThanOrEqual"] = Runtime.Py_LE, - ["op_GreaterThanOrEqual"] = Runtime.Py_GE, - ["op_LessThan"] = Runtime.Py_LT, - ["op_GreaterThan"] = Runtime.Py_GT, - }; - - /// - /// Default implementation of [] semantics for reflected types. - /// - public virtual NewReference type_subscript(BorrowedReference idx) - { - Type[]? types = Runtime.PythonArgsToTypeArray(idx); - if (types == null) - { - return Exceptions.RaiseTypeError("type(s) expected"); - } - - if (!type.Valid) - { - return Exceptions.RaiseTypeError(type.DeletedMessage); - } - - Type? target = GenericUtil.GenericForType(type.Value, types.Length); - - if (target != null) - { - Type t; - try - { - // MakeGenericType can throw ArgumentException - t = target.MakeGenericType(types); - } - catch (ArgumentException e) - { - return Exceptions.RaiseTypeError(e.Message); - } - var c = ClassManager.GetClass(t); - return new NewReference(c); - } - - return Exceptions.RaiseTypeError($"{type.Value.Namespace}.{type.Name} does not accept {types.Length} generic parameters"); - } - - /// - /// Standard comparison implementation for instances of reflected types. - /// - public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReference other, int op) - { - CLRObject co1; - CLRObject? co2; - BorrowedReference tp = Runtime.PyObject_TYPE(ob); - var cls = (ClassBase)GetManagedObject(tp)!; - // C# operator methods take precedence over IComparable. - // We first check if there's a comparison operator by looking up the richcompare table, - // otherwise fallback to checking if an IComparable interface is handled. - if (cls.richcompare.TryGetValue(op, out var methodObject)) - { - // Wrap the `other` argument of a binary comparison operator in a PyTuple. - using var args = Runtime.PyTuple_New(1); - Runtime.PyTuple_SetItem(args.Borrow(), 0, other); - return methodObject.Invoke(ob, args.Borrow(), null); - } - - switch (op) - { - case Runtime.Py_EQ: - case Runtime.Py_NE: - BorrowedReference pytrue = Runtime.PyTrue; - BorrowedReference pyfalse = Runtime.PyFalse; - - // swap true and false for NE - if (op != Runtime.Py_EQ) - { - pytrue = Runtime.PyFalse; - pyfalse = Runtime.PyTrue; - } - - if (ob == other) - { - return new NewReference(pytrue); - } - - co1 = (CLRObject)GetManagedObject(ob)!; - co2 = GetManagedObject(other) as CLRObject; - if (null == co2) - { - return new NewReference(pyfalse); - } - - object o1 = co1.inst; - object o2 = co2.inst; - - if (Equals(o1, o2)) - { - return new NewReference(pytrue); - } - - return new NewReference(pyfalse); - case Runtime.Py_LT: - case Runtime.Py_LE: - case Runtime.Py_GT: - case Runtime.Py_GE: - co1 = (CLRObject)GetManagedObject(ob)!; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; + +using Python.Runtime.Slots; + +namespace Python.Runtime +{ + /// + /// Base class for Python types that reflect managed types / classes. + /// Concrete subclasses include ClassObject and DelegateObject. This + /// class provides common attributes and common machinery for doing + /// class initialization (initialization of the class __dict__). The + /// concrete subclasses provide slot implementations appropriate for + /// each variety of reflected type. + /// + [Serializable] + internal class ClassBase : ManagedType, IDeserializationCallback + { + [NonSerialized] + internal List dotNetMembers = new(); + internal Indexer? indexer; + internal readonly Dictionary richcompare = new(); + internal MaybeType type; + + internal ClassBase(Type tp) + { + if (tp is null) throw new ArgumentNullException(nameof(type)); + + indexer = null; + type = tp; + } + + internal virtual bool CanSubclass() + { + return !type.Value.IsEnum; + } + + public readonly static Dictionary CilToPyOpMap = new Dictionary + { + ["op_Equality"] = Runtime.Py_EQ, + ["op_Inequality"] = Runtime.Py_NE, + ["op_LessThanOrEqual"] = Runtime.Py_LE, + ["op_GreaterThanOrEqual"] = Runtime.Py_GE, + ["op_LessThan"] = Runtime.Py_LT, + ["op_GreaterThan"] = Runtime.Py_GT, + }; + + /// + /// Default implementation of [] semantics for reflected types. + /// + public virtual NewReference type_subscript(BorrowedReference idx) + { + Type[]? types = Runtime.PythonArgsToTypeArray(idx); + if (types == null) + { + return Exceptions.RaiseTypeError("type(s) expected"); + } + + if (!type.Valid) + { + return Exceptions.RaiseTypeError(type.DeletedMessage); + } + + Type? target = GenericUtil.GenericForType(type.Value, types.Length); + + if (target != null) + { + Type t; + try + { + // MakeGenericType can throw ArgumentException + t = target.MakeGenericType(types); + } + catch (ArgumentException e) + { + return Exceptions.RaiseTypeError(e.Message); + } + var c = ClassManager.GetClass(t); + return new NewReference(c); + } + + return Exceptions.RaiseTypeError($"{type.Value.Namespace}.{type.Name} does not accept {types.Length} generic parameters"); + } + + /// + /// Standard comparison implementation for instances of reflected types. + /// + public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReference other, int op) + { + CLRObject co1; + CLRObject? co2; + BorrowedReference tp = Runtime.PyObject_TYPE(ob); + var cls = (ClassBase)GetManagedObject(tp)!; + // C# operator methods take precedence over IComparable. + // We first check if there's a comparison operator by looking up the richcompare table, + // otherwise fallback to checking if an IComparable interface is handled. + if (cls.richcompare.TryGetValue(op, out var methodObject)) + { + // Wrap the `other` argument of a binary comparison operator in a PyTuple. + using var args = Runtime.PyTuple_New(1); + Runtime.PyTuple_SetItem(args.Borrow(), 0, other); + return methodObject.Invoke(ob, args.Borrow(), null); + } + + switch (op) + { + case Runtime.Py_EQ: + case Runtime.Py_NE: + BorrowedReference pytrue = Runtime.PyTrue; + BorrowedReference pyfalse = Runtime.PyFalse; + + // swap true and false for NE + if (op != Runtime.Py_EQ) + { + pytrue = Runtime.PyFalse; + pyfalse = Runtime.PyTrue; + } + + if (ob == other) + { + return new NewReference(pytrue); + } + + co1 = (CLRObject)GetManagedObject(ob)!; + co2 = GetManagedObject(other) as CLRObject; + if (null == co2) + { + return new NewReference(pyfalse); + } + + object o1 = co1.inst; + object o2 = co2.inst; + + if (Equals(o1, o2)) + { + return new NewReference(pytrue); + } + + return new NewReference(pyfalse); + case Runtime.Py_LT: + case Runtime.Py_LE: + case Runtime.Py_GT: + case Runtime.Py_GE: + co1 = (CLRObject)GetManagedObject(ob)!; co2 = GetManagedObject(other) as CLRObject; object co2Inst = null; @@ -168,431 +168,431 @@ public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReferenc { co2Inst = co2.inst; } - - if (co1 == null || co2Inst == null) - { - return Exceptions.RaiseTypeError("Cannot get managed object"); - } - var co1Comp = co1.inst as IComparable; - if (co1Comp == null) - { - Type co1Type = co1.GetType(); - return Exceptions.RaiseTypeError($"Cannot convert object of type {co1Type} to IComparable"); - } - try - { - int cmp = co1Comp.CompareTo(co2Inst); - - BorrowedReference pyCmp; - if (cmp < 0) - { - if (op == Runtime.Py_LT || op == Runtime.Py_LE) - { - pyCmp = Runtime.PyTrue; - } - else - { - pyCmp = Runtime.PyFalse; - } - } - else if (cmp == 0) - { - if (op == Runtime.Py_LE || op == Runtime.Py_GE) - { - pyCmp = Runtime.PyTrue; - } - else - { - pyCmp = Runtime.PyFalse; - } - } - else - { - if (op == Runtime.Py_GE || op == Runtime.Py_GT) - { - pyCmp = Runtime.PyTrue; - } - else - { - pyCmp = Runtime.PyFalse; - } - } - return new NewReference(pyCmp); - } - catch (ArgumentException e) - { - return Exceptions.RaiseTypeError(e.Message); - } - default: - return new NewReference(Runtime.PyNotImplemented); - } - } - - /// - /// Standard iteration support for instances of reflected types. This - /// allows natural iteration over objects that either are IEnumerable - /// or themselves support IEnumerator directly. - /// - static NewReference tp_iter_impl(BorrowedReference ob) - { - var co = GetManagedObject(ob) as CLRObject; - if (co == null) - { - return Exceptions.RaiseTypeError("invalid object"); - } - - var e = co.inst as IEnumerable; - IEnumerator? o; - if (e != null) - { - o = e.GetEnumerator(); - } - else - { - o = co.inst as IEnumerator; - - if (o == null) - { - return Exceptions.RaiseTypeError("iteration over non-sequence"); - } - } - - var elemType = typeof(object); - var iterType = co.inst.GetType(); - foreach(var ifc in iterType.GetInterfaces()) - { - if (ifc.IsGenericType) - { - var genTypeDef = ifc.GetGenericTypeDefinition(); - if (genTypeDef == typeof(IEnumerable<>) || genTypeDef == typeof(IEnumerator<>)) - { - elemType = ifc.GetGenericArguments()[0]; - break; - } - } - } - - return new Iterator(o, elemType).Alloc(); - } - - - /// - /// Standard __hash__ implementation for instances of reflected types. - /// - public static nint tp_hash(BorrowedReference ob) - { - var co = GetManagedObject(ob) as CLRObject; - if (co == null) - { - Exceptions.RaiseTypeError("unhashable type"); - return 0; - } - return co.inst.GetHashCode(); - } - - - /// - /// Standard __str__ implementation for instances of reflected types. - /// - public static NewReference tp_str(BorrowedReference ob) - { - var co = GetManagedObject(ob) as CLRObject; - if (co == null) - { - return Exceptions.RaiseTypeError("invalid object"); - } - try - { - return Runtime.PyString_FromString(co.inst.ToString()); - } - catch (Exception e) - { - if (e.InnerException != null) - { - e = e.InnerException; - } - Exceptions.SetError(e); - return default; - } - } - - public static NewReference tp_repr(BorrowedReference ob) - { - var co = GetManagedObject(ob) as CLRObject; - if (co == null) - { - return Exceptions.RaiseTypeError("invalid object"); - } - try - { - //if __repr__ is defined, use it - var instType = co.inst.GetType(); - System.Reflection.MethodInfo methodInfo = instType.GetMethod("__repr__"); - if (methodInfo != null && methodInfo.IsPublic) - { - var reprString = methodInfo.Invoke(co.inst, null) as string; - return reprString is null ? new NewReference(Runtime.PyNone) : Runtime.PyString_FromString(reprString); - } - - //otherwise use the standard object.__repr__(inst) - using var args = Runtime.PyTuple_New(1); - Runtime.PyTuple_SetItem(args.Borrow(), 0, ob); - using var reprFunc = Runtime.PyObject_GetAttr(Runtime.PyBaseObjectType, PyIdentifier.__repr__); - return Runtime.PyObject_Call(reprFunc.Borrow(), args.Borrow(), null); - } - catch (Exception e) - { - if (e.InnerException != null) - { - e = e.InnerException; - } - Exceptions.SetError(e); - return default; - } - } - - - /// - /// Standard dealloc implementation for instances of reflected types. - /// - public static void tp_dealloc(NewReference lastRef) - { - Runtime.PyObject_GC_UnTrack(lastRef.Borrow()); - - CallClear(lastRef.Borrow()); - - DecrefTypeAndFree(lastRef.Steal()); - } - - public static int tp_clear(BorrowedReference ob) - { - var weakrefs = Runtime.PyObject_GetWeakRefList(ob); - if (weakrefs != null) - { - Runtime.PyObject_ClearWeakRefs(ob); - } - - TryFreeGCHandle(ob); - - int baseClearResult = BaseUnmanagedClear(ob); - if (baseClearResult != 0) - { - return baseClearResult; - } - - ClearObjectDict(ob); - return 0; - } - - internal static unsafe int BaseUnmanagedClear(BorrowedReference ob) - { - var type = Runtime.PyObject_TYPE(ob); - var unmanagedBase = GetUnmanagedBaseType(type); - var clearPtr = Util.ReadIntPtr(unmanagedBase, TypeOffset.tp_clear); - if (clearPtr == IntPtr.Zero) - { - return 0; - } - var clear = (delegate* unmanaged[Cdecl])clearPtr; - - bool usesSubtypeClear = clearPtr == TypeManager.subtype_clear; - if (usesSubtypeClear) - { - // workaround for https://bugs.python.org/issue45266 (subtype_clear) - using var dict = Runtime.PyObject_GenericGetDict(ob); - if (Runtime.PyMapping_HasKey(dict.Borrow(), PyIdentifier.__clear_reentry_guard__) != 0) - return 0; - int res = Runtime.PyDict_SetItem(dict.Borrow(), PyIdentifier.__clear_reentry_guard__, Runtime.None); - if (res != 0) return res; - - res = clear(ob); - Runtime.PyDict_DelItem(dict.Borrow(), PyIdentifier.__clear_reentry_guard__); - return res; - } - return clear(ob); - } - - protected override Dictionary OnSave(BorrowedReference ob) - { - var context = base.OnSave(ob) ?? new(); - context["impl"] = this; - return context; - } - - protected override void OnLoad(BorrowedReference ob, Dictionary? context) - { - base.OnLoad(ob, context); - var gcHandle = GCHandle.Alloc(this); - SetGCHandle(ob, gcHandle); - } - - - /// - /// Implements __getitem__ for reflected classes and value types. - /// - static NewReference mp_subscript_impl(BorrowedReference ob, BorrowedReference idx) - { - BorrowedReference tp = Runtime.PyObject_TYPE(ob); - var cls = (ClassBase)GetManagedObject(tp)!; - - if (cls.indexer == null || !cls.indexer.CanGet) - { - Exceptions.SetError(Exceptions.TypeError, "unindexable object"); - return default; - } - - // Arg may be a tuple in the case of an indexer with multiple - // parameters. If so, use it directly, else make a new tuple - // with the index arg (method binders expect arg tuples). - if (!Runtime.PyTuple_Check(idx)) - { - using var argTuple = Runtime.PyTuple_New(1); - Runtime.PyTuple_SetItem(argTuple.Borrow(), 0, idx); - return cls.indexer.GetItem(ob, argTuple.Borrow()); - } - else - { - return cls.indexer.GetItem(ob, idx); - } - } - - - /// - /// Implements __setitem__ for reflected classes and value types. - /// - static int mp_ass_subscript_impl(BorrowedReference ob, BorrowedReference idx, BorrowedReference v) - { - BorrowedReference tp = Runtime.PyObject_TYPE(ob); - var cls = (ClassBase)GetManagedObject(tp)!; - - if (cls.indexer == null || !cls.indexer.CanSet) - { - Exceptions.SetError(Exceptions.TypeError, "object doesn't support item assignment"); - return -1; - } - - // Arg may be a tuple in the case of an indexer with multiple - // parameters. If so, use it directly, else make a new tuple - // with the index arg (method binders expect arg tuples). - NewReference argsTuple = default; - - if (!Runtime.PyTuple_Check(idx)) - { - argsTuple = Runtime.PyTuple_New(1); - Runtime.PyTuple_SetItem(argsTuple.Borrow(), 0, idx); - idx = argsTuple.Borrow(); - } - - // Get the args passed in. - var i = Runtime.PyTuple_Size(idx); - using var defaultArgs = cls.indexer.GetDefaultArgs(idx); - var numOfDefaultArgs = Runtime.PyTuple_Size(defaultArgs.Borrow()); - var temp = i + numOfDefaultArgs; - using var real = Runtime.PyTuple_New(temp + 1); - for (var n = 0; n < i; n++) - { - BorrowedReference item = Runtime.PyTuple_GetItem(idx, n); - Runtime.PyTuple_SetItem(real.Borrow(), n, item); - } - - argsTuple.Dispose(); - - // Add Default Args if needed - for (var n = 0; n < numOfDefaultArgs; n++) - { - BorrowedReference item = Runtime.PyTuple_GetItem(defaultArgs.Borrow(), n); - Runtime.PyTuple_SetItem(real.Borrow(), n + i, item); - } - i = temp; - - // Add value to argument list - Runtime.PyTuple_SetItem(real.Borrow(), i, v); - - cls.indexer.SetItem(ob, real.Borrow()); - - if (Exceptions.ErrorOccurred()) - { - return -1; - } - - return 0; - } - - static NewReference tp_call_impl(BorrowedReference ob, BorrowedReference args, BorrowedReference kw) - { - BorrowedReference tp = Runtime.PyObject_TYPE(ob); - var self = (ClassBase)GetManagedObject(tp)!; - - if (!self.type.Valid) - { - return Exceptions.RaiseTypeError(self.type.DeletedMessage); - } - - Type type = self.type.Value; - - var calls = GetCallImplementations(type).ToList(); - Debug.Assert(calls.Count > 0); - var callBinder = new MethodBinder(); - foreach (MethodInfo call in calls) - { - callBinder.AddMethod(call, true); - } - return callBinder.Invoke(ob, args, kw); - } - - static IEnumerable GetCallImplementations(Type type) - => type.GetMethods(BindingFlags.Public | BindingFlags.Instance) - .Where(m => m.Name == "__call__"); - - public virtual void InitializeSlots(BorrowedReference pyType, SlotsHolder slotsHolder) - { - if (!this.type.Valid) return; - - if (GetCallImplementations(this.type.Value).Any()) - { - TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.tp_call, new Interop.BBB_N(tp_call_impl), slotsHolder); - } - - if (indexer is not null) - { - if (indexer.CanGet) - { - TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.mp_subscript, new Interop.BB_N(mp_subscript_impl), slotsHolder); - } - if (indexer.CanSet) - { - TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.mp_ass_subscript, new Interop.BBB_I32(mp_ass_subscript_impl), slotsHolder); - } - } - - if (typeof(IEnumerable).IsAssignableFrom(type.Value) - || typeof(IEnumerator).IsAssignableFrom(type.Value)) - { - TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.tp_iter, new Interop.B_N(tp_iter_impl), slotsHolder); - } - - if (MpLengthSlot.CanAssign(type.Value)) - { - TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.mp_length, new Interop.B_P(MpLengthSlot.impl), slotsHolder); - } - } - - public virtual bool HasCustomNew() => this.GetType().GetMethod("tp_new") is not null; - - public override bool Init(BorrowedReference obj, BorrowedReference args, BorrowedReference kw) - { - if (this.HasCustomNew()) - // initialization must be done in tp_new - return true; - - return base.Init(obj, args, kw); - } - - protected virtual void OnDeserialization(object sender) - { - this.dotNetMembers = new List(); - } - - void IDeserializationCallback.OnDeserialization(object sender) => this.OnDeserialization(sender); - } -} + + if (co1 == null || co2Inst == null) + { + return Exceptions.RaiseTypeError("Cannot get managed object"); + } + var co1Comp = co1.inst as IComparable; + if (co1Comp == null) + { + Type co1Type = co1.GetType(); + return Exceptions.RaiseTypeError($"Cannot convert object of type {co1Type} to IComparable"); + } + try + { + int cmp = co1Comp.CompareTo(co2Inst); + + BorrowedReference pyCmp; + if (cmp < 0) + { + if (op == Runtime.Py_LT || op == Runtime.Py_LE) + { + pyCmp = Runtime.PyTrue; + } + else + { + pyCmp = Runtime.PyFalse; + } + } + else if (cmp == 0) + { + if (op == Runtime.Py_LE || op == Runtime.Py_GE) + { + pyCmp = Runtime.PyTrue; + } + else + { + pyCmp = Runtime.PyFalse; + } + } + else + { + if (op == Runtime.Py_GE || op == Runtime.Py_GT) + { + pyCmp = Runtime.PyTrue; + } + else + { + pyCmp = Runtime.PyFalse; + } + } + return new NewReference(pyCmp); + } + catch (ArgumentException e) + { + return Exceptions.RaiseTypeError(e.Message); + } + default: + return new NewReference(Runtime.PyNotImplemented); + } + } + + /// + /// Standard iteration support for instances of reflected types. This + /// allows natural iteration over objects that either are IEnumerable + /// or themselves support IEnumerator directly. + /// + static NewReference tp_iter_impl(BorrowedReference ob) + { + var co = GetManagedObject(ob) as CLRObject; + if (co == null) + { + return Exceptions.RaiseTypeError("invalid object"); + } + + var e = co.inst as IEnumerable; + IEnumerator? o; + if (e != null) + { + o = e.GetEnumerator(); + } + else + { + o = co.inst as IEnumerator; + + if (o == null) + { + return Exceptions.RaiseTypeError("iteration over non-sequence"); + } + } + + var elemType = typeof(object); + var iterType = co.inst.GetType(); + foreach(var ifc in iterType.GetInterfaces()) + { + if (ifc.IsGenericType) + { + var genTypeDef = ifc.GetGenericTypeDefinition(); + if (genTypeDef == typeof(IEnumerable<>) || genTypeDef == typeof(IEnumerator<>)) + { + elemType = ifc.GetGenericArguments()[0]; + break; + } + } + } + + return new Iterator(o, elemType).Alloc(); + } + + + /// + /// Standard __hash__ implementation for instances of reflected types. + /// + public static nint tp_hash(BorrowedReference ob) + { + var co = GetManagedObject(ob) as CLRObject; + if (co == null) + { + Exceptions.RaiseTypeError("unhashable type"); + return 0; + } + return co.inst.GetHashCode(); + } + + + /// + /// Standard __str__ implementation for instances of reflected types. + /// + public static NewReference tp_str(BorrowedReference ob) + { + var co = GetManagedObject(ob) as CLRObject; + if (co == null) + { + return Exceptions.RaiseTypeError("invalid object"); + } + try + { + return Runtime.PyString_FromString(co.inst.ToString()); + } + catch (Exception e) + { + if (e.InnerException != null) + { + e = e.InnerException; + } + Exceptions.SetError(e); + return default; + } + } + + public static NewReference tp_repr(BorrowedReference ob) + { + var co = GetManagedObject(ob) as CLRObject; + if (co == null) + { + return Exceptions.RaiseTypeError("invalid object"); + } + try + { + //if __repr__ is defined, use it + var instType = co.inst.GetType(); + System.Reflection.MethodInfo methodInfo = instType.GetMethod("__repr__"); + if (methodInfo != null && methodInfo.IsPublic) + { + var reprString = methodInfo.Invoke(co.inst, null) as string; + return reprString is null ? new NewReference(Runtime.PyNone) : Runtime.PyString_FromString(reprString); + } + + //otherwise use the standard object.__repr__(inst) + using var args = Runtime.PyTuple_New(1); + Runtime.PyTuple_SetItem(args.Borrow(), 0, ob); + using var reprFunc = Runtime.PyObject_GetAttr(Runtime.PyBaseObjectType, PyIdentifier.__repr__); + return Runtime.PyObject_Call(reprFunc.Borrow(), args.Borrow(), null); + } + catch (Exception e) + { + if (e.InnerException != null) + { + e = e.InnerException; + } + Exceptions.SetError(e); + return default; + } + } + + + /// + /// Standard dealloc implementation for instances of reflected types. + /// + public static void tp_dealloc(NewReference lastRef) + { + Runtime.PyObject_GC_UnTrack(lastRef.Borrow()); + + CallClear(lastRef.Borrow()); + + DecrefTypeAndFree(lastRef.Steal()); + } + + public static int tp_clear(BorrowedReference ob) + { + var weakrefs = Runtime.PyObject_GetWeakRefList(ob); + if (weakrefs != null) + { + Runtime.PyObject_ClearWeakRefs(ob); + } + + TryFreeGCHandle(ob); + + int baseClearResult = BaseUnmanagedClear(ob); + if (baseClearResult != 0) + { + return baseClearResult; + } + + ClearObjectDict(ob); + return 0; + } + + internal static unsafe int BaseUnmanagedClear(BorrowedReference ob) + { + var type = Runtime.PyObject_TYPE(ob); + var unmanagedBase = GetUnmanagedBaseType(type); + var clearPtr = Util.ReadIntPtr(unmanagedBase, TypeOffset.tp_clear); + if (clearPtr == IntPtr.Zero) + { + return 0; + } + var clear = (delegate* unmanaged[Cdecl])clearPtr; + + bool usesSubtypeClear = clearPtr == TypeManager.subtype_clear; + if (usesSubtypeClear) + { + // workaround for https://bugs.python.org/issue45266 (subtype_clear) + using var dict = Runtime.PyObject_GenericGetDict(ob); + if (Runtime.PyMapping_HasKey(dict.Borrow(), PyIdentifier.__clear_reentry_guard__) != 0) + return 0; + int res = Runtime.PyDict_SetItem(dict.Borrow(), PyIdentifier.__clear_reentry_guard__, Runtime.None); + if (res != 0) return res; + + res = clear(ob); + Runtime.PyDict_DelItem(dict.Borrow(), PyIdentifier.__clear_reentry_guard__); + return res; + } + return clear(ob); + } + + protected override Dictionary OnSave(BorrowedReference ob) + { + var context = base.OnSave(ob) ?? new(); + context["impl"] = this; + return context; + } + + protected override void OnLoad(BorrowedReference ob, Dictionary? context) + { + base.OnLoad(ob, context); + var gcHandle = GCHandle.Alloc(this); + SetGCHandle(ob, gcHandle); + } + + + /// + /// Implements __getitem__ for reflected classes and value types. + /// + static NewReference mp_subscript_impl(BorrowedReference ob, BorrowedReference idx) + { + BorrowedReference tp = Runtime.PyObject_TYPE(ob); + var cls = (ClassBase)GetManagedObject(tp)!; + + if (cls.indexer == null || !cls.indexer.CanGet) + { + Exceptions.SetError(Exceptions.TypeError, "unindexable object"); + return default; + } + + // Arg may be a tuple in the case of an indexer with multiple + // parameters. If so, use it directly, else make a new tuple + // with the index arg (method binders expect arg tuples). + if (!Runtime.PyTuple_Check(idx)) + { + using var argTuple = Runtime.PyTuple_New(1); + Runtime.PyTuple_SetItem(argTuple.Borrow(), 0, idx); + return cls.indexer.GetItem(ob, argTuple.Borrow()); + } + else + { + return cls.indexer.GetItem(ob, idx); + } + } + + + /// + /// Implements __setitem__ for reflected classes and value types. + /// + static int mp_ass_subscript_impl(BorrowedReference ob, BorrowedReference idx, BorrowedReference v) + { + BorrowedReference tp = Runtime.PyObject_TYPE(ob); + var cls = (ClassBase)GetManagedObject(tp)!; + + if (cls.indexer == null || !cls.indexer.CanSet) + { + Exceptions.SetError(Exceptions.TypeError, "object doesn't support item assignment"); + return -1; + } + + // Arg may be a tuple in the case of an indexer with multiple + // parameters. If so, use it directly, else make a new tuple + // with the index arg (method binders expect arg tuples). + NewReference argsTuple = default; + + if (!Runtime.PyTuple_Check(idx)) + { + argsTuple = Runtime.PyTuple_New(1); + Runtime.PyTuple_SetItem(argsTuple.Borrow(), 0, idx); + idx = argsTuple.Borrow(); + } + + // Get the args passed in. + var i = Runtime.PyTuple_Size(idx); + using var defaultArgs = cls.indexer.GetDefaultArgs(idx); + var numOfDefaultArgs = Runtime.PyTuple_Size(defaultArgs.Borrow()); + var temp = i + numOfDefaultArgs; + using var real = Runtime.PyTuple_New(temp + 1); + for (var n = 0; n < i; n++) + { + BorrowedReference item = Runtime.PyTuple_GetItem(idx, n); + Runtime.PyTuple_SetItem(real.Borrow(), n, item); + } + + argsTuple.Dispose(); + + // Add Default Args if needed + for (var n = 0; n < numOfDefaultArgs; n++) + { + BorrowedReference item = Runtime.PyTuple_GetItem(defaultArgs.Borrow(), n); + Runtime.PyTuple_SetItem(real.Borrow(), n + i, item); + } + i = temp; + + // Add value to argument list + Runtime.PyTuple_SetItem(real.Borrow(), i, v); + + cls.indexer.SetItem(ob, real.Borrow()); + + if (Exceptions.ErrorOccurred()) + { + return -1; + } + + return 0; + } + + static NewReference tp_call_impl(BorrowedReference ob, BorrowedReference args, BorrowedReference kw) + { + BorrowedReference tp = Runtime.PyObject_TYPE(ob); + var self = (ClassBase)GetManagedObject(tp)!; + + if (!self.type.Valid) + { + return Exceptions.RaiseTypeError(self.type.DeletedMessage); + } + + Type type = self.type.Value; + + var calls = GetCallImplementations(type).ToList(); + Debug.Assert(calls.Count > 0); + var callBinder = new MethodBinder(); + foreach (MethodInfo call in calls) + { + callBinder.AddMethod(call, true); + } + return callBinder.Invoke(ob, args, kw); + } + + static IEnumerable GetCallImplementations(Type type) + => type.GetMethods(BindingFlags.Public | BindingFlags.Instance) + .Where(m => m.Name == "__call__"); + + public virtual void InitializeSlots(BorrowedReference pyType, SlotsHolder slotsHolder) + { + if (!this.type.Valid) return; + + if (GetCallImplementations(this.type.Value).Any()) + { + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.tp_call, new Interop.BBB_N(tp_call_impl), slotsHolder); + } + + if (indexer is not null) + { + if (indexer.CanGet) + { + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.mp_subscript, new Interop.BB_N(mp_subscript_impl), slotsHolder); + } + if (indexer.CanSet) + { + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.mp_ass_subscript, new Interop.BBB_I32(mp_ass_subscript_impl), slotsHolder); + } + } + + if (typeof(IEnumerable).IsAssignableFrom(type.Value) + || typeof(IEnumerator).IsAssignableFrom(type.Value)) + { + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.tp_iter, new Interop.B_N(tp_iter_impl), slotsHolder); + } + + if (MpLengthSlot.CanAssign(type.Value)) + { + TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.mp_length, new Interop.B_P(MpLengthSlot.impl), slotsHolder); + } + } + + public virtual bool HasCustomNew() => this.GetType().GetMethod("tp_new") is not null; + + public override bool Init(BorrowedReference obj, BorrowedReference args, BorrowedReference kw) + { + if (this.HasCustomNew()) + // initialization must be done in tp_new + return true; + + return base.Init(obj, args, kw); + } + + protected virtual void OnDeserialization(object sender) + { + this.dotNetMembers = new List(); + } + + void IDeserializationCallback.OnDeserialization(object sender) => this.OnDeserialization(sender); + } +} From 2ab66923aa2bdcc0da56f8b0cc09054f3ce459f2 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Thu, 26 Sep 2024 17:01:23 -0400 Subject: [PATCH 4/8] Try EQ and NE comparison with python object if conversion to managed is not possible --- src/runtime/Types/ClassBase.cs | 52 +++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/src/runtime/Types/ClassBase.cs b/src/runtime/Types/ClassBase.cs index 8df43efbf..8d6b6948f 100644 --- a/src/runtime/Types/ClassBase.cs +++ b/src/runtime/Types/ClassBase.cs @@ -128,14 +128,13 @@ public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReferenc } co1 = (CLRObject)GetManagedObject(ob)!; - co2 = GetManagedObject(other) as CLRObject; - if (null == co2) + var o2 = GetSecondCompareOperandInstance(other); + if (null == o2) { return new NewReference(pyfalse); } object o1 = co1.inst; - object o2 = co2.inst; if (Equals(o1, o2)) { @@ -148,26 +147,7 @@ public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReferenc case Runtime.Py_GT: case Runtime.Py_GE: co1 = (CLRObject)GetManagedObject(ob)!; - co2 = GetManagedObject(other) as CLRObject; - - object co2Inst = null; - // The object comparing against is not a managed object. It could still be a Python object - // that can be compared against (e.g. comparing against a Python string) - if (co2 == null) - { - if (other != null) - { - using var pyCo2 = new PyObject(other); - if (Converter.ToManagedValue(pyCo2, typeof(object), out var result, false)) - { - co2Inst = result; - } - } - } - else - { - co2Inst = co2.inst; - } + var co2Inst = GetSecondCompareOperandInstance(other); if (co1 == null || co2Inst == null) { @@ -228,6 +208,32 @@ public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReferenc } } + private static object GetSecondCompareOperandInstance(BorrowedReference other) + { + var co2 = GetManagedObject(other) as CLRObject; + + object co2Inst = null; + // The object comparing against is not a managed object. It could still be a Python object + // that can be compared against (e.g. comparing against a Python string) + if (co2 == null) + { + if (other != null) + { + using var pyCo2 = new PyObject(other); + if (Converter.ToManagedValue(pyCo2, typeof(object), out var result, false)) + { + co2Inst = result; + } + } + } + else + { + co2Inst = co2.inst; + } + + return co2Inst; + } + /// /// Standard iteration support for instances of reflected types. This /// allows natural iteration over objects that either are IEnumerable From 69583dea1b5a1db162edfcf1dc1e07437840dd67 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Thu, 26 Sep 2024 18:28:56 -0400 Subject: [PATCH 5/8] Address peer review --- src/runtime/Types/ClassBase.cs | 52 +++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/src/runtime/Types/ClassBase.cs b/src/runtime/Types/ClassBase.cs index 8d6b6948f..ac39220fe 100644 --- a/src/runtime/Types/ClassBase.cs +++ b/src/runtime/Types/ClassBase.cs @@ -95,6 +95,9 @@ public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReferenc { CLRObject co1; CLRObject? co2; + object co1Inst; + object co2Inst; + NewReference error; BorrowedReference tp = Runtime.PyObject_TYPE(ob); var cls = (ClassBase)GetManagedObject(tp)!; // C# operator methods take precedence over IComparable. @@ -127,16 +130,14 @@ public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReferenc return new NewReference(pytrue); } - co1 = (CLRObject)GetManagedObject(ob)!; - var o2 = GetSecondCompareOperandInstance(other); - if (null == o2) + GetSecondCompareOperandInstance(ob, other, out co1, out co2, out co1Inst, out co2Inst, out error); + + if (co2Inst == null) { return new NewReference(pyfalse); } - object o1 = co1.inst; - - if (Equals(o1, o2)) + if (Equals(co1Inst, co2Inst)) { return new NewReference(pytrue); } @@ -146,14 +147,14 @@ public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReferenc case Runtime.Py_LE: case Runtime.Py_GT: case Runtime.Py_GE: - co1 = (CLRObject)GetManagedObject(ob)!; - var co2Inst = GetSecondCompareOperandInstance(other); + GetSecondCompareOperandInstance(ob, other, out co1, out co2, out co1Inst, out co2Inst, out error); - if (co1 == null || co2Inst == null) + if (!error.IsNone() && !error.IsNull()) { return Exceptions.RaiseTypeError("Cannot get managed object"); } - var co1Comp = co1.inst as IComparable; + + var co1Comp = co1Inst as IComparable; if (co1Comp == null) { Type co1Type = co1.GetType(); @@ -208,22 +209,36 @@ public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReferenc } } - private static object GetSecondCompareOperandInstance(BorrowedReference other) + private static void GetSecondCompareOperandInstance(BorrowedReference left, BorrowedReference right, + out CLRObject co1, out CLRObject co2, out object co1Inst, out object co2Inst, out NewReference error) { - var co2 = GetManagedObject(other) as CLRObject; + co1Inst = null; + co2Inst = null; + error = new NewReference(Runtime.PyNone); - object co2Inst = null; + co1 = (CLRObject)GetManagedObject(left)!; + co2 = GetManagedObject(right) as CLRObject; + + var co2IsValid = true; // The object comparing against is not a managed object. It could still be a Python object // that can be compared against (e.g. comparing against a Python string) if (co2 == null) { - if (other != null) + if (right != null) { - using var pyCo2 = new PyObject(other); + using var pyCo2 = new PyObject(right); if (Converter.ToManagedValue(pyCo2, typeof(object), out var result, false)) { co2Inst = result; } + else + { + co2IsValid = false; + } + } + else + { + co2IsValid = false; } } else @@ -231,7 +246,12 @@ private static object GetSecondCompareOperandInstance(BorrowedReference other) co2Inst = co2.inst; } - return co2Inst; + if (co1 == null || !co2IsValid) + { + error = Exceptions.RaiseTypeError("Cannot get managed object"); + } + + co1Inst = co1.inst; } /// From 39b4db321bddb3e26008b8e57f6be742236261a1 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Fri, 27 Sep 2024 12:51:08 -0400 Subject: [PATCH 6/8] Cleanup --- src/runtime/Types/ClassBase.cs | 45 ++++++++++++---------------------- 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/src/runtime/Types/ClassBase.cs b/src/runtime/Types/ClassBase.cs index ac39220fe..f726e931c 100644 --- a/src/runtime/Types/ClassBase.cs +++ b/src/runtime/Types/ClassBase.cs @@ -130,14 +130,14 @@ public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReferenc return new NewReference(pytrue); } - GetSecondCompareOperandInstance(ob, other, out co1, out co2, out co1Inst, out co2Inst, out error); + TryGetSecondCompareOperandInstance(ob, other, out co1, out co2Inst); if (co2Inst == null) { return new NewReference(pyfalse); } - if (Equals(co1Inst, co2Inst)) + if (Equals(co1.inst, co2Inst)) { return new NewReference(pytrue); } @@ -147,14 +147,12 @@ public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReferenc case Runtime.Py_LE: case Runtime.Py_GT: case Runtime.Py_GE: - GetSecondCompareOperandInstance(ob, other, out co1, out co2, out co1Inst, out co2Inst, out error); - - if (!error.IsNone() && !error.IsNull()) + if (!TryGetSecondCompareOperandInstance(ob, other, out co1, out co2Inst)) { return Exceptions.RaiseTypeError("Cannot get managed object"); } - var co1Comp = co1Inst as IComparable; + var co1Comp = co1.inst as IComparable; if (co1Comp == null) { Type co1Type = co1.GetType(); @@ -209,17 +207,18 @@ public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReferenc } } - private static void GetSecondCompareOperandInstance(BorrowedReference left, BorrowedReference right, - out CLRObject co1, out CLRObject co2, out object co1Inst, out object co2Inst, out NewReference error) + private static bool TryGetSecondCompareOperandInstance(BorrowedReference left, BorrowedReference right, out CLRObject co1, out object co2Inst) { - co1Inst = null; co2Inst = null; - error = new NewReference(Runtime.PyNone); co1 = (CLRObject)GetManagedObject(left)!; - co2 = GetManagedObject(right) as CLRObject; + if (co1 == null) + { + return false; + } + + var co2 = GetManagedObject(right) as CLRObject; - var co2IsValid = true; // The object comparing against is not a managed object. It could still be a Python object // that can be compared against (e.g. comparing against a Python string) if (co2 == null) @@ -230,28 +229,14 @@ private static void GetSecondCompareOperandInstance(BorrowedReference left, Borr if (Converter.ToManagedValue(pyCo2, typeof(object), out var result, false)) { co2Inst = result; + return true; } - else - { - co2IsValid = false; - } - } - else - { - co2IsValid = false; } - } - else - { - co2Inst = co2.inst; - } - - if (co1 == null || !co2IsValid) - { - error = Exceptions.RaiseTypeError("Cannot get managed object"); + return false; } - co1Inst = co1.inst; + co2Inst = co2.inst; + return true; } /// From 20f89773d8081a871a26840a9e0ad7cec513b841 Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Fri, 27 Sep 2024 13:16:37 -0400 Subject: [PATCH 7/8] Minor fix --- src/runtime/Types/ClassBase.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/runtime/Types/ClassBase.cs b/src/runtime/Types/ClassBase.cs index f726e931c..f9b974d59 100644 --- a/src/runtime/Types/ClassBase.cs +++ b/src/runtime/Types/ClassBase.cs @@ -94,8 +94,6 @@ public virtual NewReference type_subscript(BorrowedReference idx) public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReference other, int op) { CLRObject co1; - CLRObject? co2; - object co1Inst; object co2Inst; NewReference error; BorrowedReference tp = Runtime.PyObject_TYPE(ob); @@ -130,9 +128,7 @@ public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReferenc return new NewReference(pytrue); } - TryGetSecondCompareOperandInstance(ob, other, out co1, out co2Inst); - - if (co2Inst == null) + if (!TryGetSecondCompareOperandInstance(ob, other, out co1, out co2Inst)) { return new NewReference(pyfalse); } From d83c4d2af92e6f2922dc0f0b5205d0c597186d4e Mon Sep 17 00:00:00 2001 From: Jhonathan Abreu Date: Fri, 27 Sep 2024 15:03:03 -0400 Subject: [PATCH 8/8] Cleanup --- src/runtime/Types/ClassBase.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/runtime/Types/ClassBase.cs b/src/runtime/Types/ClassBase.cs index f9b974d59..ded315952 100644 --- a/src/runtime/Types/ClassBase.cs +++ b/src/runtime/Types/ClassBase.cs @@ -95,7 +95,6 @@ public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReferenc { CLRObject co1; object co2Inst; - NewReference error; BorrowedReference tp = Runtime.PyObject_TYPE(ob); var cls = (ClassBase)GetManagedObject(tp)!; // C# operator methods take precedence over IComparable.