X Tutup
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace Python.Runtime { using MaybeMethodInfo = MaybeMethodBase; /// /// Implements a Python type that represents a CLR method. Method objects /// support a subscript syntax [] to allow explicit overload selection. /// /// /// TODO: ForbidPythonThreadsAttribute per method info /// [Serializable] internal class MethodObject : ExtensionType { [NonSerialized] private MethodBase[]? _info = null; private readonly List infoList; internal string name; internal readonly MethodBinder binder; internal bool is_static = false; internal PyString? doc; internal MaybeType type; public MethodObject(MaybeType type, string name, MethodBase[] info, bool allow_threads, bool argsReversed = false) { this.type = type; this.name = name; this.infoList = new List(); binder = new MethodBinder() { argsReversed = argsReversed }; foreach (MethodBase item in info) { this.infoList.Add(item); binder.AddMethod(item); if (item.IsStatic) { this.is_static = true; } } binder.allow_threads = allow_threads; } public MethodObject(MaybeType type, string name, MethodBase[] info, bool argsReversed = false) : this(type, name, info, allow_threads: AllowThreads(info), argsReversed) { } public bool IsInstanceConstructor => name == "__init__"; public MethodObject WithOverloads(MethodBase[] overloads) => new(type, name, overloads, allow_threads: binder.allow_threads); internal MethodBase[] info { get { if (_info == null) { _info = (from i in infoList where i.Valid select i.Value).ToArray(); } return _info; } } public virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw) { return Invoke(inst, args, kw, null); } public virtual NewReference Invoke(BorrowedReference target, BorrowedReference args, BorrowedReference kw, MethodBase? info) { return binder.Invoke(target, args, kw, info, this.info); } /// /// Helper to get docstrings from reflected method / param info. /// internal NewReference GetDocString() { if (doc is not null) { return new NewReference(doc); } var str = ""; Type marker = typeof(DocStringAttribute); MethodBase[] methods = binder.GetMethods(); foreach (MethodBase method in methods) { if (str.Length > 0) { str += Environment.NewLine; } var attrs = (Attribute[])method.GetCustomAttributes(marker, false); if (attrs.Length == 0) { str += method.ToString(); } else { var attr = (DocStringAttribute)attrs[0]; str += attr.DocString; } } doc = new PyString(str); return new NewReference(doc); } internal NewReference GetName() { var names = new HashSet(binder.GetMethods().Select(m => m.Name)); if (names.Count != 1) { Exceptions.SetError(Exceptions.AttributeError, "a method has no name"); return default; } return Runtime.PyString_FromString(names.First()); } /// /// This is a little tricky: a class can actually have a static method /// and instance methods all with the same name. That makes it tough /// to support calling a method 'unbound' (passing the instance as the /// first argument), because in this case we can't know whether to call /// the instance method unbound or call the static method. /// /// /// The rule we is that if there are both instance and static methods /// with the same name, then we always call the static method. So this /// method returns true if any of the methods that are represented by /// the descriptor are static methods (called by MethodBinding). /// internal bool IsStatic() { return is_static; } /// /// Descriptor __getattribute__ implementation. /// public static NewReference tp_getattro(BorrowedReference ob, BorrowedReference key) { var self = (MethodObject)GetManagedObject(ob)!; if (!Runtime.PyString_Check(key)) { return Exceptions.RaiseTypeError("string expected"); } if (Runtime.PyUnicode_Compare(key, PyIdentifier.__doc__) == 0) { return self.GetDocString(); } return Runtime.PyObject_GenericGetAttr(ob, key); } /// /// Descriptor __get__ implementation. Accessing a CLR method returns /// a "bound" method similar to a Python bound method. /// public static NewReference tp_descr_get(BorrowedReference ds, BorrowedReference ob, BorrowedReference tp) { var self = (MethodObject)GetManagedObject(ds)!; if (!self.type.Valid) { return Exceptions.RaiseTypeError(self.type.DeletedMessage); } // If the method is accessed through its type (rather than via // an instance) we return an 'unbound' MethodBinding that will // cached for future accesses through the type. if (ob == null) { var binding = new MethodBinding(self, target: null, targetType: new PyType(tp)); return binding.Alloc(); } if (Runtime.PyObject_IsInstance(ob, tp) < 1) { return Exceptions.RaiseTypeError("invalid argument"); } // If the object this descriptor is being called with is a subclass of the type // this descriptor was defined on then it will be because the base class method // is being called via super(Derived, self).method(...). // In which case create a MethodBinding bound to the base class. if (GetManagedObject(ob) is CLRObject obj && obj.inst.GetType() != self.type.Value && obj.inst is IPythonDerivedType && self.type.Value.IsInstanceOfType(obj.inst)) { var basecls = ReflectedClrType.GetOrCreate(self.type.Value); return new MethodBinding(self, new PyObject(ob), basecls).Alloc(); } return new MethodBinding(self, target: new PyObject(ob), targetType: new PyType(tp)).Alloc(); } /// /// Descriptor __repr__ implementation. /// public static NewReference tp_repr(BorrowedReference ob) { var self = (MethodObject)GetManagedObject(ob)!; return Runtime.PyString_FromString($""); } static bool AllowThreads(MethodBase[] methods) { bool hasAllowOverload = false, hasForbidOverload = false; foreach (var method in methods) { bool forbidsThreads = method.GetCustomAttribute(inherit: false) != null; if (forbidsThreads) { hasForbidOverload = true; } else { hasAllowOverload = true; } } if (hasAllowOverload && hasForbidOverload) throw new NotImplementedException("All method overloads currently must either allow or forbid Python threads together"); return !hasForbidOverload; } } }
X Tutup