using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Python.Runtime
{
using MaybeMethodInfo = MaybeMethodBase;
///
/// Implements a Python binding type for CLR methods. These work much like
/// standard Python method bindings, but the same type is used to bind
/// both static and instance methods.
///
[Serializable]
internal class MethodBinding : ExtensionType
{
internal MaybeMethodInfo info;
internal MethodObject m;
internal PyObject? target;
internal PyType? targetType;
public MethodBinding(MethodObject m, PyObject? target, PyType? targetType = null)
{
this.target = target;
this.targetType = targetType ?? target?.GetPythonType();
this.info = null;
this.m = m;
}
///
/// Implement binding of generic methods using the subscript syntax [].
///
public static NewReference mp_subscript(BorrowedReference tp, BorrowedReference idx)
{
var self = (MethodBinding)GetManagedObject(tp)!;
Type[]? types = Runtime.PythonArgsToTypeArray(idx);
if (types == null)
{
return Exceptions.RaiseTypeError("type(s) expected");
}
MethodInfo? mi = MethodBinder.MatchParameters(self.m.info, types);
if (mi == null)
{
return Exceptions.RaiseTypeError("No match found for given type params");
}
var mb = new MethodBinding(self.m, self.target, self.targetType) { info = mi };
return mb.Alloc();
}
PyObject Signature
{
get
{
var infos = this.info.Valid ? new[] { this.info.Value } : this.m.info;
Type type = infos.Select(i => i.DeclaringType)
.OrderByDescending(t => t, new TypeSpecificityComparer())
.First();
infos = infos.Where(info => info.DeclaringType == type).ToArray();
// this is a primitive version
// the overload with the maximum number of parameters should be used
MethodInfo primary = infos.OrderByDescending(i => i.GetParameters().Length).First();
var primaryParameters = primary.GetParameters();
PyObject signatureClass = Runtime.InspectModule.GetAttr("Signature");
var primaryReturn = primary.ReturnParameter;
using var parameters = new PyList();
using var parameterClass = primaryParameters.Length > 0 ? Runtime.InspectModule.GetAttr("Parameter") : null;
using var positionalOrKeyword = parameterClass?.GetAttr("POSITIONAL_OR_KEYWORD");
for (int i = 0; i < primaryParameters.Length; i++)
{
var parameter = primaryParameters[i];
var alternatives = infos.Select(info =>
{
ParameterInfo[] altParamters = info.GetParameters();
return i < altParamters.Length ? altParamters[i] : null;
}).WhereNotNull();
using var defaultValue = alternatives
.Select(alternative => alternative!.DefaultValue != DBNull.Value ? alternative.DefaultValue.ToPython() : null)
.FirstOrDefault(v => v != null) ?? parameterClass?.GetAttr("empty");
if (alternatives.Any(alternative => alternative.Name != parameter.Name)
|| positionalOrKeyword is null)
{
return signatureClass.Invoke();
}
using var args = new PyTuple(new[] { parameter.Name.ToPython(), positionalOrKeyword });
using var kw = new PyDict();
if (defaultValue is not null)
{
kw["default"] = defaultValue;
}
using var parameterInfo = parameterClass!.Invoke(args: args, kw: kw);
parameters.Append(parameterInfo);
}
// TODO: add return annotation
return signatureClass.Invoke(parameters);
}
}
struct TypeSpecificityComparer : IComparer
{
public int Compare(Type a, Type b)
{
if (a == b) return 0;
if (a.IsSubclassOf(b)) return 1;
if (b.IsSubclassOf(a)) return -1;
throw new NotSupportedException();
}
}
///
/// MethodBinding __getattribute__ implementation.
///
public static NewReference tp_getattro(BorrowedReference ob, BorrowedReference key)
{
var self = (MethodBinding)GetManagedObject(ob)!;
if (!Runtime.PyString_Check(key))
{
Exceptions.SetError(Exceptions.TypeError, "string expected");
return default;
}
string? name = InternString.GetManagedString(key);
switch (name)
{
case "__doc__":
return self.m.GetDocString();
// FIXME: deprecate __overloads__ soon...
case "__overloads__":
case "Overloads":
var om = new OverloadMapper(self.m, self.target);
return om.Alloc();
case "__signature__" when Runtime.InspectModule is not null:
var sig = self.Signature;
if (sig is null)
{
return Runtime.PyObject_GenericGetAttr(ob, key);
}
return sig.NewReferenceOrNull();
case "__name__":
return self.m.GetName();
default:
return Runtime.PyObject_GenericGetAttr(ob, key);
}
}
///
/// MethodBinding __call__ implementation.
///
public static NewReference tp_call(BorrowedReference ob, BorrowedReference args, BorrowedReference kw)
{
var self = (MethodBinding)GetManagedObject(ob)!;
// This works around a situation where the wrong generic method is picked,
// for example this method in the tests: string Overloaded(int arg1, int arg2, string arg3)
if (self.info.Valid)
{
var info = self.info.Value;
if (info.IsGenericMethod)
{
var len = Runtime.PyTuple_Size(args); //FIXME: Never used
Type[]? sigTp = Runtime.PythonArgsToTypeArray(args, true);
if (sigTp != null)
{
Type[] genericTp = info.GetGenericArguments();
MethodInfo? betterMatch = MethodBinder.MatchSignatureAndParameters(self.m.info, genericTp, sigTp);
if (betterMatch != null)
{
self.info = betterMatch;
}
}
}
}
// This supports calling a method 'unbound', passing the instance
// as the first argument. Note that this is not supported if any
// of the overloads are static since we can't know if the intent
// was to call the static method or the unbound instance method.
var disposeList = new List();
try
{
PyObject? target = self.target;
if (target is null && !self.m.IsStatic())
{
var len = Runtime.PyTuple_Size(args);
if (len < 1)
{
Exceptions.SetError(Exceptions.TypeError, "not enough arguments");
return default;
}
target = new PyObject(Runtime.PyTuple_GetItem(args, 0));
disposeList.Add(target);
var unboundArgs = Runtime.PyTuple_GetSlice(args, 1, len).MoveToPyObject();
disposeList.Add(unboundArgs);
args = unboundArgs;
}
// if the class is a IPythonDerivedClass and target is not the same as self.targetType
// (eg if calling the base class method) then call the original base class method instead
// of the target method.
IntPtr superType = IntPtr.Zero;
if (target is not null && Runtime.PyObject_TYPE(target) != self.targetType!)
{
var inst = GetManagedObject(target) as CLRObject;
if (inst?.inst is IPythonDerivedType)
{
var baseType = GetManagedObject(self.targetType!) as ClassBase;
if (baseType != null && baseType.type.Valid)
{
string baseMethodName = "_" + baseType.type.Value.Name + "__" + self.m.name;
using var baseMethod = Runtime.PyObject_GetAttrString(target, baseMethodName);
if (!baseMethod.IsNull())
{
var baseSelf = GetManagedObject(baseMethod.Borrow()) as MethodBinding;
if (baseSelf != null)
{
self = baseSelf;
}
}
else
{
Runtime.PyErr_Clear();
}
}
}
}
return self.m.Invoke(target is null ? BorrowedReference.Null : target, args, kw, self.info.UnsafeValue);
}
finally
{
foreach (var ptr in disposeList)
{
ptr.Dispose();
}
}
}
///
/// MethodBinding __hash__ implementation.
///
public static nint tp_hash(BorrowedReference ob)
{
var self = (MethodBinding)GetManagedObject(ob)!;
nint x = 0;
if (self.target is not null)
{
x = Runtime.PyObject_Hash(self.target);
if (x == -1)
{
return x;
}
}
nint y = self.m.GetHashCode();
return x ^ y;
}
///
/// MethodBinding __repr__ implementation.
///
public static NewReference tp_repr(BorrowedReference ob)
{
var self = (MethodBinding)GetManagedObject(ob)!;
string type = self.target is null ? "unbound" : "bound";
string name = self.m.name;
return Runtime.PyString_FromString($"<{type} method '{name}'>");
}
}
}