using System;
using System.Collections;
using System.Reflection;
using System.Text;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace Python.Runtime
{
using MaybeMethodBase = MaybeMethodBase;
///
/// A MethodBinder encapsulates information about a (possibly overloaded)
/// managed method, and is responsible for selecting the right method given
/// a set of Python arguments. This is also used as a base class for the
/// ConstructorBinder, a minor variation used to invoke constructors.
///
[Serializable]
internal class MethodBinder
{
///
/// The overloads of this method
///
public List list;
[NonSerialized]
public MethodBase[]? methods;
[NonSerialized]
public bool init = false;
public const bool DefaultAllowThreads = true;
public bool allow_threads = DefaultAllowThreads;
public bool argsReversed = false;
internal MethodBinder()
{
list = new List();
}
internal MethodBinder(MethodInfo mi)
{
list = new List { new MaybeMethodBase(mi) };
}
public int Count
{
get { return list.Count; }
}
internal void AddMethod(MethodBase m)
{
list.Add(m);
}
internal void AddRange(IEnumerable methods)
{
list.AddRange(methods.Select(m => new MaybeMethodBase(m)));
}
///
/// Given a sequence of MethodInfo and a sequence of types, return the
/// MethodInfo that matches the signature represented by those types.
///
internal static MethodBase? MatchSignature(MethodBase[] mi, Type[] tp)
{
if (tp == null)
{
return null;
}
int count = tp.Length;
foreach (MethodBase t in mi)
{
ParameterInfo[] pi = t.GetParameters();
if (pi.Length != count)
{
continue;
}
for (var n = 0; n < pi.Length; n++)
{
if (tp[n] != pi[n].ParameterType)
{
break;
}
if (n == pi.Length - 1)
{
return t;
}
}
}
return null;
}
///
/// Given a sequence of MethodInfo and a sequence of type parameters,
/// return the MethodInfo(s) that represents the matching closed generic.
/// If unsuccessful, returns null and may set a Python error.
///
internal static MethodInfo[] MatchParameters(MethodBase[] mi, Type[]? tp)
{
if (tp == null)
{
return Array.Empty();
}
int count = tp.Length;
var result = new List();
foreach (MethodInfo t in mi)
{
if (!t.IsGenericMethodDefinition)
{
continue;
}
Type[] args = t.GetGenericArguments();
if (args.Length != count)
{
continue;
}
try
{
// MakeGenericMethod can throw ArgumentException if the type parameters do not obey the constraints.
MethodInfo method = t.MakeGenericMethod(tp);
result.Add(method);
}
catch (ArgumentException)
{
// The error will remain set until cleared by a successful match.
}
}
return result.ToArray();
}
///
/// Given a sequence of MethodInfo and two sequences of type parameters,
/// return the MethodInfo that matches the signature and the closed generic.
///
internal static MethodInfo? MatchSignatureAndParameters(MethodBase[] mi, Type[] genericTp, Type[] sigTp)
{
if (genericTp == null || sigTp == null)
{
return null;
}
int genericCount = genericTp.Length;
int signatureCount = sigTp.Length;
foreach (MethodInfo t in mi)
{
if (!t.IsGenericMethodDefinition)
{
continue;
}
Type[] genericArgs = t.GetGenericArguments();
if (genericArgs.Length != genericCount)
{
continue;
}
ParameterInfo[] pi = t.GetParameters();
if (pi.Length != signatureCount)
{
continue;
}
for (var n = 0; n < pi.Length; n++)
{
if (sigTp[n] != pi[n].ParameterType)
{
break;
}
if (n == pi.Length - 1)
{
MethodInfo match = t;
if (match.IsGenericMethodDefinition)
{
// FIXME: typeArgs not used
Type[] typeArgs = match.GetGenericArguments();
return match.MakeGenericMethod(genericTp);
}
return match;
}
}
}
return null;
}
///
/// Return the array of MethodInfo for this method. The result array
/// is arranged in order of precedence (done lazily to avoid doing it
/// at all for methods that are never called).
///
internal MethodBase[] GetMethods()
{
if (!init)
{
// I'm sure this could be made more efficient.
list.Sort(new MethodSorter());
methods = (from method in list where method.Valid select method.Value).ToArray();
init = true;
}
return methods!;
}
///
/// Precedence algorithm largely lifted from Jython - the concerns are
/// generally the same so we'll start with this and tweak as necessary.
///
///
/// Based from Jython `org.python.core.ReflectedArgs.precedence`
/// See: https://github.com/jythontools/jython/blob/master/src/org/python/core/ReflectedArgs.java#L192
///
internal static int GetPrecedence(MethodBase mi)
{
if (mi == null)
{
return int.MaxValue;
}
ParameterInfo[] pi = mi.GetParameters();
int val = mi.IsStatic ? 3000 : 0;
int num = pi.Length;
val += mi.IsGenericMethod ? 1 : 0;
for (var i = 0; i < num; i++)
{
val += ArgPrecedence(pi[i].ParameterType);
}
return val;
}
///
/// Return a precedence value for a particular Type object.
///
internal static int ArgPrecedence(Type t)
{
Type objectType = typeof(object);
if (t == objectType)
{
return 3000;
}
if (t.IsArray)
{
Type e = t.GetElementType();
if (e == objectType)
{
return 2500;
}
return 100 + ArgPrecedence(e);
}
TypeCode tc = Type.GetTypeCode(t);
// TODO: Clean up
return tc switch
{
TypeCode.Object => 1,
TypeCode.UInt64 => 10,
TypeCode.UInt32 => 11,
TypeCode.UInt16 => 12,
TypeCode.Int64 => 13,
TypeCode.Int32 => 14,
TypeCode.Int16 => 15,
TypeCode.Char => 16,
TypeCode.SByte => 17,
TypeCode.Byte => 18,
TypeCode.Single => 20,
TypeCode.Double => 21,
TypeCode.String => 30,
TypeCode.Boolean => 40,
_ => 2000,
};
}
///
/// Bind the given Python instance and arguments to a particular method
/// overload in and return a structure that contains the converted Python
/// instance, converted arguments and the correct method to call.
/// If unsuccessful, may set a Python error.
///
/// The Python target of the method invocation.
/// The Python arguments.
/// The Python keyword arguments.
/// A Binding if successful. Otherwise null.
internal Binding? Bind(BorrowedReference inst, BorrowedReference args, BorrowedReference kw)
{
return Bind(inst, args, kw, null, null);
}
///
/// Bind the given Python instance and arguments to a particular method
/// overload in and return a structure that contains the converted Python
/// instance, converted arguments and the correct method to call.
/// If unsuccessful, may set a Python error.
///
/// The Python target of the method invocation.
/// The Python arguments.
/// The Python keyword arguments.
/// If not null, only bind to that method.
/// A Binding if successful. Otherwise null.
internal Binding? Bind(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info)
{
return Bind(inst, args, kw, info, null);
}
private readonly struct MatchedMethod
{
public MatchedMethod(int kwargsMatched, int defaultsNeeded, object?[] margs, int outs, MethodBase mb)
{
KwargsMatched = kwargsMatched;
DefaultsNeeded = defaultsNeeded;
ManagedArgs = margs;
Outs = outs;
Method = mb;
}
public int KwargsMatched { get; }
public int DefaultsNeeded { get; }
public object?[] ManagedArgs { get; }
public int Outs { get; }
public MethodBase Method { get; }
}
private readonly struct MismatchedMethod
{
public MismatchedMethod(Exception exception, MethodBase mb)
{
Exception = exception;
Method = mb;
}
public Exception Exception { get; }
public MethodBase Method { get; }
}
///
/// Bind the given Python instance and arguments to a particular method
/// overload in and return a structure that contains the converted Python
/// instance, converted arguments and the correct method to call.
/// If unsuccessful, may set a Python error.
///
/// The Python target of the method invocation.
/// The Python arguments.
/// The Python keyword arguments.
/// If not null, only bind to that method.
/// If not null, additionally attempt to bind to the generic methods in this array by inferring generic type parameters.
/// A Binding if successful. Otherwise null.
internal Binding? Bind(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info, MethodBase[]? methodinfo)
{
// loop to find match, return invoker w/ or w/o error
var kwargDict = new Dictionary();
if (kw != null)
{
nint pynkwargs = Runtime.PyDict_Size(kw);
using var keylist = Runtime.PyDict_Keys(kw);
using var valueList = Runtime.PyDict_Values(kw);
for (int i = 0; i < pynkwargs; ++i)
{
var keyStr = Runtime.GetManagedString(Runtime.PyList_GetItem(keylist.Borrow(), i));
BorrowedReference value = Runtime.PyList_GetItem(valueList.Borrow(), i);
kwargDict[keyStr!] = new PyObject(value);
}
}
MethodBase[] _methods;
if (info != null)
{
_methods = new MethodBase[1];
_methods.SetValue(info, 0);
}
else
{
_methods = GetMethods();
}
return Bind(inst, args, kwargDict, _methods, matchGenerics: true, argsReversed);
}
private static Binding? Bind(BorrowedReference inst, BorrowedReference args, Dictionary kwargDict, MethodBase[] methods, bool matchGenerics, bool argsReversed = false)
{
var pynargs = (int)Runtime.PyTuple_Size(args);
var isGeneric = false;
var argMatchedMethods = new List(methods.Length);
var mismatchedMethods = new List();
// TODO: Clean up
foreach (MethodBase mi in methods)
{
if (mi.IsGenericMethod)
{
isGeneric = true;
}
ParameterInfo[] pi = mi.GetParameters();
bool isOperator = OperatorMethod.IsOperatorMethod(mi);
// Binary operator methods will have 2 CLR args but only one Python arg
// (unary operators will have 1 less each), since Python operator methods are bound.
isOperator = isOperator && pynargs == pi.Length - 1;
bool isReverse = isOperator && argsReversed; // Only cast if isOperator.
if (isReverse && OperatorMethod.IsComparisonOp((MethodInfo)mi))
continue; // Comparison operators in Python have no reverse mode.
if (!MatchesArgumentCount(pynargs, pi, kwargDict, out bool paramsArray, out ArrayList? defaultArgList, out int kwargsMatched, out int defaultsNeeded) && !isOperator)
{
continue;
}
// Preprocessing pi to remove either the first or second argument.
if (isOperator && !isReverse)
{
// The first Python arg is the right operand, while the bound instance is the left.
// We need to skip the first (left operand) CLR argument.
pi = pi.Skip(1).ToArray();
}
else if (isOperator && isReverse)
{
// The first Python arg is the left operand.
// We need to take the first CLR argument.
pi = pi.Take(1).ToArray();
}
var margs = TryConvertArguments(pi, paramsArray, args, pynargs, kwargDict, defaultArgList, outs: out int outs);
if (margs == null)
{
var mismatchCause = PythonException.FetchCurrent();
mismatchedMethods.Add(new MismatchedMethod(mismatchCause, mi));
continue;
}
if (isOperator)
{
if (inst != null)
{
if (ManagedType.GetManagedObject(inst) is CLRObject co)
{
bool isUnary = pynargs == 0;
// Postprocessing to extend margs.
var margsTemp = isUnary ? new object?[1] : new object?[2];
// If reverse, the bound instance is the right operand.
int boundOperandIndex = isReverse ? 1 : 0;
// If reverse, the passed instance is the left operand.
int passedOperandIndex = isReverse ? 0 : 1;
margsTemp[boundOperandIndex] = co.inst;
if (!isUnary)
{
margsTemp[passedOperandIndex] = margs[0];
}
margs = margsTemp;
}
else continue;
}
}
var matchedMethod = new MatchedMethod(kwargsMatched, defaultsNeeded, margs, outs, mi);
argMatchedMethods.Add(matchedMethod);
}
if (argMatchedMethods.Count > 0)
{
var bestKwargMatchCount = argMatchedMethods.Max(x => x.KwargsMatched);
var fewestDefaultsRequired = argMatchedMethods.Where(x => x.KwargsMatched == bestKwargMatchCount).Min(x => x.DefaultsNeeded);
int bestCount = 0;
int bestMatchIndex = -1;
for (int index = 0; index < argMatchedMethods.Count; index++)
{
var testMatch = argMatchedMethods[index];
if (testMatch.DefaultsNeeded == fewestDefaultsRequired && testMatch.KwargsMatched == bestKwargMatchCount)
{
bestCount++;
if (bestMatchIndex == -1)
bestMatchIndex = index;
}
}
if (bestCount > 1 && fewestDefaultsRequired > 0)
{
// Best effort for determining method to match on gives multiple possible
// matches and we need at least one default argument - bail from this point
var stringBuilder = new StringBuilder("Not enough arguments provided to disambiguate the method. Found:");
foreach (var matchedMethod in argMatchedMethods)
{
stringBuilder.AppendLine();
stringBuilder.Append(matchedMethod.Method.ToString());
}
Exceptions.SetError(Exceptions.TypeError, stringBuilder.ToString());
return null;
}
// If we're here either:
// (a) There is only one best match
// (b) There are multiple best matches but none of them require
// default arguments
// in the case of (a) we're done by default. For (b) regardless of which
// method we choose, all arguments are specified _and_ can be converted
// from python to C# so picking any will suffice
MatchedMethod bestMatch = argMatchedMethods[bestMatchIndex];
var margs = bestMatch.ManagedArgs;
var outs = bestMatch.Outs;
var mi = bestMatch.Method;
object? target = null;
if (!mi.IsStatic && inst != null)
{
//CLRObject co = (CLRObject)ManagedType.GetManagedObject(inst);
// InvalidCastException: Unable to cast object of type
// 'Python.Runtime.ClassObject' to type 'Python.Runtime.CLRObject'
// Sanity check: this ensures a graceful exit if someone does
// something intentionally wrong like call a non-static method
// on the class rather than on an instance of the class.
// XXX maybe better to do this before all the other rigmarole.
if (ManagedType.GetManagedObject(inst) is CLRObject co)
{
target = co.inst;
}
else
{
Exceptions.SetError(Exceptions.TypeError, "Invoked a non-static method with an invalid instance");
return null;
}
}
return new Binding(mi, target, margs, outs);
}
else if (matchGenerics && isGeneric)
{
// We weren't able to find a matching method but at least one
// is a generic method and info is null. That happens when a generic
// method was not called using the [] syntax. Let's introspect the
// type of the arguments and use it to construct the correct method.
Type[]? types = Runtime.PythonArgsToTypeArray(args, true);
MethodInfo[] overloads = MatchParameters(methods, types);
if (overloads.Length != 0)
{
return Bind(inst, args, kwargDict, overloads, matchGenerics: false);
}
}
if (mismatchedMethods.Count > 0)
{
var aggregateException = GetAggregateException(mismatchedMethods);
Exceptions.SetError(aggregateException);
}
return null;
}
static AggregateException GetAggregateException(IEnumerable mismatchedMethods)
{
return new AggregateException(mismatchedMethods.Select(m => new ArgumentException($"{m.Exception.Message} in method {m.Method}", m.Exception)));
}
static BorrowedReference HandleParamsArray(BorrowedReference args, int arrayStart, int pyArgCount, out NewReference tempObject)
{
BorrowedReference op;
tempObject = default;
// for a params method, we may have a sequence or single/multiple items
// here we look to see if the item at the paramIndex is there or not
// and then if it is a sequence itself.
if ((pyArgCount - arrayStart) == 1)
{
// we only have one argument left, so we need to check it
// to see if it is a sequence or a single item
BorrowedReference item = Runtime.PyTuple_GetItem(args, arrayStart);
if (!Runtime.PyString_Check(item) && Runtime.PySequence_Check(item))
{
// it's a sequence (and not a string), so we use it as the op
op = item;
}
else
{
tempObject = Runtime.PyTuple_GetSlice(args, arrayStart, pyArgCount);
op = tempObject.Borrow();
}
}
else
{
tempObject = Runtime.PyTuple_GetSlice(args, arrayStart, pyArgCount);
op = tempObject.Borrow();
}
return op;
}
///
/// Attempts to convert Python positional argument tuple and keyword argument table
/// into an array of managed objects, that can be passed to a method.
/// If unsuccessful, returns null and may set a Python error.
///
/// Information about expected parameters
/// true, if the last parameter is a params array.
/// A pointer to the Python argument tuple
/// Number of arguments, passed by Python
/// Dictionary of keyword argument name to python object pointer
/// A list of default values for omitted parameters
/// Returns number of output parameters
/// If successful, an array of .NET arguments that can be passed to the method. Otherwise null.
static object?[]? TryConvertArguments(ParameterInfo[] pi, bool paramsArray,
BorrowedReference args, int pyArgCount,
Dictionary kwargDict,
ArrayList? defaultArgList,
out int outs)
{
outs = 0;
var margs = new object?[pi.Length];
int arrayStart = paramsArray ? pi.Length - 1 : -1;
for (int paramIndex = 0; paramIndex < pi.Length; paramIndex++)
{
var parameter = pi[paramIndex];
bool hasNamedParam = parameter.Name != null && kwargDict.ContainsKey(parameter.Name);
if (paramIndex >= pyArgCount && !(hasNamedParam || (paramsArray && paramIndex == arrayStart)))
{
if (defaultArgList != null)
{
margs[paramIndex] = defaultArgList[paramIndex - pyArgCount];
}
if (parameter.ParameterType.IsByRef)
{
outs++;
}
continue;
}
BorrowedReference op;
NewReference tempObject = default;
if (hasNamedParam)
{
op = kwargDict[parameter.Name!];
}
else
{
if(arrayStart == paramIndex)
{
op = HandleParamsArray(args, arrayStart, pyArgCount, out tempObject);
}
else
{
op = Runtime.PyTuple_GetItem(args, paramIndex);
}
}
if (!TryConvertArgument(op, parameter.ParameterType, out margs[paramIndex], out bool isOut))
{
tempObject.Dispose();
return null;
}
tempObject.Dispose();
if (isOut)
{
outs++;
}
}
return margs;
}
///
/// Try to convert a Python argument object to a managed CLR type.
/// If unsuccessful, may set a Python error.
///
/// Pointer to the Python argument object.
/// That parameter's managed type.
/// Converted argument.
/// Whether the CLR type is passed by reference.
/// true on success
static bool TryConvertArgument(BorrowedReference op, Type parameterType,
out object? arg, out bool isOut)
{
arg = null;
isOut = false;
var clrtype = TryComputeClrArgumentType(parameterType, op);
if (clrtype == null)
{
return false;
}
if (!Converter.ToManaged(op, clrtype, out arg, true))
{
return false;
}
isOut = clrtype.IsByRef;
return true;
}
///
/// Determine the managed type that a Python argument object needs to be converted into.
///
/// The parameter's managed type.
/// Pointer to the Python argument object.
/// null if conversion is not possible
static Type? TryComputeClrArgumentType(Type parameterType, BorrowedReference argument)
{
// this logic below handles cases when multiple overloading methods
// are ambiguous, hence comparison between Python and CLR types
// is necessary
Type? clrtype = null;
if (clrtype != null)
{
if ((parameterType != typeof(object)) && (parameterType != clrtype))
{
BorrowedReference pytype = Converter.GetPythonTypeByAlias(parameterType);
BorrowedReference pyoptype = Runtime.PyObject_TYPE(argument);
var typematch = false;
if (pyoptype != null)
{
if (pytype != pyoptype)
{
typematch = false;
}
else
{
typematch = true;
clrtype = parameterType;
}
}
if (!typematch)
{
// this takes care of enum values
TypeCode parameterTypeCode = Type.GetTypeCode(parameterType);
TypeCode clrTypeCode = Type.GetTypeCode(clrtype);
if (parameterTypeCode == clrTypeCode)
{
typematch = true;
clrtype = parameterType;
}
else
{
Exceptions.RaiseTypeError($"Expected {parameterTypeCode}, got {clrTypeCode}");
}
}
if (!typematch)
{
return null;
}
}
else
{
clrtype = parameterType;
}
}
else
{
clrtype = parameterType;
}
return clrtype;
}
///
/// Check whether the number of Python and .NET arguments match, and compute additional arg information.
///
/// Number of positional args passed from Python.
/// Parameters of the specified .NET method.
/// Keyword args passed from Python.
/// True if the final param of the .NET method is an array (`params` keyword).
/// List of default values for arguments.
/// Number of kwargs from Python that are also present in the .NET method.
/// Number of non-null defaultsArgs.
///
static bool MatchesArgumentCount(int positionalArgumentCount, ParameterInfo[] parameters,
Dictionary kwargDict,
out bool paramsArray,
out ArrayList? defaultArgList,
out int kwargsMatched,
out int defaultsNeeded)
{
defaultArgList = null;
var match = false;
paramsArray = parameters.Length > 0 && Attribute.IsDefined(parameters[parameters.Length - 1], typeof(ParamArrayAttribute));
kwargsMatched = 0;
defaultsNeeded = 0;
if (positionalArgumentCount == parameters.Length && kwargDict.Count == 0)
{
match = true;
}
else if (positionalArgumentCount < parameters.Length && (!paramsArray || positionalArgumentCount == parameters.Length - 1))
{
match = true;
// every parameter past 'positionalArgumentCount' must have either
// a corresponding keyword arg or a default param, unless the method
// method accepts a params array (which cannot have a default value)
defaultArgList = new ArrayList();
for (var v = positionalArgumentCount; v < parameters.Length; v++)
{
if (kwargDict.ContainsKey(parameters[v].Name))
{
// we have a keyword argument for this parameter,
// no need to check for a default parameter, but put a null
// placeholder in defaultArgList
defaultArgList.Add(null);
kwargsMatched++;
}
else if (parameters[v].IsOptional)
{
// IsOptional will be true if the parameter has a default value,
// or if the parameter has the [Optional] attribute specified.
// The GetDefaultValue() extension method will return the value
// to be passed in as the parameter value
defaultArgList.Add(parameters[v].GetDefaultValue());
defaultsNeeded++;
}
else if (parameters[v].IsOut) {
defaultArgList.Add(null);
}
else if (!paramsArray)
{
match = false;
}
}
}
else if (positionalArgumentCount > parameters.Length && parameters.Length > 0 &&
Attribute.IsDefined(parameters[parameters.Length - 1], typeof(ParamArrayAttribute)))
{
// This is a `foo(params object[] bar)` style method
match = true;
paramsArray = true;
}
return match;
}
internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw)
{
return Invoke(inst, args, kw, null, null);
}
internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info)
{
return Invoke(inst, args, kw, info, null);
}
protected static void AppendArgumentTypes(StringBuilder to, BorrowedReference args)
{
Runtime.AssertNoErorSet();
nint argCount = Runtime.PyTuple_Size(args);
to.Append("(");
for (nint argIndex = 0; argIndex < argCount; argIndex++)
{
BorrowedReference arg = Runtime.PyTuple_GetItem(args, argIndex);
if (arg != null)
{
BorrowedReference type = Runtime.PyObject_TYPE(arg);
if (type != null)
{
using var description = Runtime.PyObject_Str(type);
if (description.IsNull())
{
Exceptions.Clear();
to.Append(Util.BadStr);
}
else
{
to.Append(Runtime.GetManagedString(description.Borrow()));
}
}
}
if (argIndex + 1 < argCount)
to.Append(", ");
}
to.Append(')');
}
internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference args, BorrowedReference kw, MethodBase? info, MethodBase[]? methodinfo)
{
// No valid methods, nothing to bind.
if (GetMethods().Length == 0)
{
var msg = new StringBuilder("The underlying C# method(s) have been deleted");
if (list.Count > 0 && list[0].Name != null)
{
msg.Append($": {list[0]}");
}
return Exceptions.RaiseTypeError(msg.ToString());
}
Binding? binding = Bind(inst, args, kw, info, methodinfo);
object result;
IntPtr ts = IntPtr.Zero;
if (binding == null)
{
var value = new StringBuilder("No method matches given arguments");
if (methodinfo != null && methodinfo.Length > 0)
{
value.Append($" for {methodinfo[0].DeclaringType?.Name}.{methodinfo[0].Name}");
}
else if (list.Count > 0 && list[0].Valid)
{
value.Append($" for {list[0].Value.DeclaringType?.Name}.{list[0].Value.Name}");
}
value.Append(": ");
Runtime.PyErr_Fetch(out var errType, out var errVal, out var errTrace);
AppendArgumentTypes(to: value, args);
Runtime.PyErr_Restore(errType.StealNullable(), errVal.StealNullable(), errTrace.StealNullable());
return Exceptions.RaiseTypeError(value.ToString());
}
if (allow_threads)
{
ts = PythonEngine.BeginAllowThreads();
}
try
{
result = binding.info.Invoke(binding.inst, BindingFlags.Default, null, binding.args, null);
}
catch (Exception e)
{
if (e.InnerException != null)
{
e = e.InnerException;
}
if (allow_threads)
{
PythonEngine.EndAllowThreads(ts);
}
Exceptions.SetError(e);
return default;
}
if (allow_threads)
{
PythonEngine.EndAllowThreads(ts);
}
// If there are out parameters, we return a tuple containing
// the result, if any, followed by the out parameters. If there is only
// one out parameter and the return type of the method is void,
// we return the out parameter as the result to Python (for
// code compatibility with ironpython).
var returnType = binding.info.IsConstructor ? typeof(void) : ((MethodInfo)binding.info).ReturnType;
if (binding.outs > 0)
{
ParameterInfo[] pi = binding.info.GetParameters();
int c = pi.Length;
var n = 0;
bool isVoid = returnType == typeof(void);
int tupleSize = binding.outs + (isVoid ? 0 : 1);
using var t = Runtime.PyTuple_New(tupleSize);
if (!isVoid)
{
using var v = Converter.ToPython(result, returnType);
Runtime.PyTuple_SetItem(t.Borrow(), n, v.Steal());
n++;
}
for (var i = 0; i < c; i++)
{
Type pt = pi[i].ParameterType;
if (pt.IsByRef)
{
using var v = Converter.ToPython(binding.args[i], pt.GetElementType());
Runtime.PyTuple_SetItem(t.Borrow(), n, v.Steal());
n++;
}
}
if (binding.outs == 1 && returnType == typeof(void))
{
BorrowedReference item = Runtime.PyTuple_GetItem(t.Borrow(), 0);
return new NewReference(item);
}
return new NewReference(t.Borrow());
}
return Converter.ToPython(result, returnType);
}
}
///
/// Utility class to sort method info by parameter type precedence.
///
internal class MethodSorter : IComparer
{
int IComparer.Compare(MaybeMethodBase m1, MaybeMethodBase m2)
{
MethodBase me1 = m1.UnsafeValue;
MethodBase me2 = m2.UnsafeValue;
if (me1 == null && me2 == null)
{
return 0;
}
else if (me1 == null)
{
return -1;
}
else if (me2 == null)
{
return 1;
}
if (me1.DeclaringType != me2.DeclaringType)
{
// m2's type derives from m1's type, favor m2
if (me1.DeclaringType.IsAssignableFrom(me2.DeclaringType))
return 1;
// m1's type derives from m2's type, favor m1
if (me2.DeclaringType.IsAssignableFrom(me1.DeclaringType))
return -1;
}
int p1 = MethodBinder.GetPrecedence(me1);
int p2 = MethodBinder.GetPrecedence(me2);
if (p1 < p2)
{
return -1;
}
if (p1 > p2)
{
return 1;
}
return 0;
}
}
///
/// A Binding is a utility instance that bundles together a MethodInfo
/// representing a method to call, a (possibly null) target instance for
/// the call, and the arguments for the call (all as managed values).
///
internal class Binding
{
public MethodBase info;
public object?[] args;
public object? inst;
public int outs;
internal Binding(MethodBase info, object? inst, object?[] args, int outs)
{
this.info = info;
this.inst = inst;
this.args = args;
this.outs = outs;
}
}
static internal class ParameterInfoExtensions
{
public static object? GetDefaultValue(this ParameterInfo parameterInfo)
{
if (parameterInfo.HasDefaultValue)
{
return parameterInfo.DefaultValue;
}
else
{
// [OptionalAttribute] was specified for the parameter.
// See https://stackoverflow.com/questions/3416216/optionalattribute-parameters-default-value
// for rules on determining the value to pass to the parameter
var type = parameterInfo.ParameterType;
if (type == typeof(object))
return Type.Missing;
else if (type.IsValueType)
return Activator.CreateInstance(type);
else
return null;
}
}
}
}