X Tutup
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 MethodBinder? del; 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() { ["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; if (co1 == null || co2 == null) { return Exceptions.RaiseTypeError("Cannot get managed object"); } if (co1.inst is not IComparable co1Comp) { Type co1Type = co1.GetType(); return Exceptions.RaiseTypeError($"Cannot convert object of type {co1Type} to IComparable"); } try { int cmp = co1Comp.CompareTo(co2.inst); 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) { if (GetManagedObject(ob) is not CLRObject co) { return Exceptions.RaiseTypeError("invalid object"); } IEnumerator? o; if (co.inst is IEnumerable e) { 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) { if (GetManagedObject(ob) is CLRObject co) { return co.inst.GetHashCode(); } else { Exceptions.RaiseTypeError("unhashable type"); return 0; } } /// /// 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) { if (GetManagedObject(ob) is not CLRObject co) { return Exceptions.RaiseTypeError("invalid object"); } try { //if __repr__ is defined, use it var instType = co.inst.GetType(); var methodInfo = instType.GetMethod("__repr__"); if (methodInfo != null && methodInfo.IsPublic) { if (methodInfo.Invoke(co.inst, null) is string reprString) { return Runtime.PyString_FromString(reprString); } else { return new NewReference(Runtime.PyNone); } } //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); } if (TryFreeGCHandle(ob)) { IntPtr addr = ob.DangerousGetAddress(); bool deleted = CLRObject.reflectedObjects.Remove(addr); Debug.Assert(deleted); } 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 (v.IsNull) { return DelImpl(ob, idx, cls); } 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); using var result = cls.indexer.SetItem(ob, real.Borrow()); return result.IsNull() ? -1 : 0; } /// Implements __delitem__ (del x[...]) for IList<T> and IDictionary<TKey, TValue>. private static int DelImpl(BorrowedReference ob, BorrowedReference idx, ClassBase cls) { if (cls.del is null) { Exceptions.SetError(Exceptions.TypeError, "object does not support item deletion"); return -1; } if (Runtime.PyTuple_Check(idx)) { Exceptions.SetError(Exceptions.TypeError, "multi-index deletion not supported"); return -1; } using var argsTuple = Runtime.PyTuple_New(1); Runtime.PyTuple_SetItem(argsTuple.Borrow(), 0, idx); using var result = cls.del.Invoke(ob, argsTuple.Borrow(), kw: null); if (result.IsNull()) return -1; if (Runtime.PyBool_CheckExact(result.Borrow())) { if (Runtime.PyObject_IsTrue(result.Borrow()) != 0) return 0; Exceptions.SetError(Exceptions.KeyError, "key not found"); return -1; } if (!result.IsNone()) { Exceptions.warn("unsupported return type for __delitem__", Exceptions.TypeError); } 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); } return callBinder.Invoke(ob, args, kw); } static NewReference DoConvert(BorrowedReference ob) { var self = (CLRObject)GetManagedObject(ob)!; using var python = self.inst.ToPython(); return python.NewReferenceOrNull(); } static NewReference DoConvertInt(BorrowedReference ob) { var self = (CLRObject)GetManagedObject(ob)!; return Runtime.PyLong_FromLongLong(Convert.ToInt64(self.inst)); } static NewReference DoConvertUInt(BorrowedReference ob) { var self = (CLRObject)GetManagedObject(ob)!; return Runtime.PyLong_FromUnsignedLongLong(Convert.ToUInt64(self.inst)); } static NewReference DoConvertBooleanInt(BorrowedReference ob) { var self = (CLRObject)GetManagedObject(ob)!; return Runtime.PyInt_FromInt32((bool)self.inst ? 1 : 0); } static NewReference DoConvertFloat(BorrowedReference ob) { var self = (CLRObject)GetManagedObject(ob)!; return Runtime.PyFloat_FromDouble(Convert.ToDouble(self.inst)); } static IEnumerable GetCallImplementations(Type type) => type.GetMethods(BindingFlags.Public | BindingFlags.Instance) .Where(m => m.Name == "__call__"); 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); } switch (Type.GetTypeCode(type.Value)) { case TypeCode.Boolean: case TypeCode.SByte: case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_int, new Interop.B_N(DoConvertInt), slotsHolder); TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_index, new Interop.B_N(DoConvertInt), slotsHolder); TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_float, new Interop.B_N(DoConvertFloat), slotsHolder); break; case TypeCode.Byte: case TypeCode.UInt16: case TypeCode.UInt32: case TypeCode.UInt64: TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_int, new Interop.B_N(DoConvertUInt), slotsHolder); TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_index, new Interop.B_N(DoConvertUInt), slotsHolder); TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_float, new Interop.B_N(DoConvertFloat), slotsHolder); break; case TypeCode.Double: case TypeCode.Single: TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_int, new Interop.B_N(DoConvertInt), slotsHolder); TypeManager.InitializeSlotIfEmpty(pyType, TypeOffset.nb_float, new Interop.B_N(DoConvertFloat), slotsHolder); break; } } public virtual bool HasCustomNew() => this.GetType().GetMethod("tp_new") is not null; 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); } }
X Tutup