using System;
using System.Collections;
using System.Reflection;
namespace Python.Runtime
{
///
/// 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.
///
internal class MethodBinder
{
public ArrayList list;
public MethodBase[] methods;
public bool init = false;
public bool allow_threads = true;
internal MethodBinder()
{
list = new ArrayList();
}
internal MethodBinder(MethodInfo mi)
{
list = new ArrayList { mi };
}
public int Count
{
get { return list.Count; }
}
internal void AddMethod(MethodBase m)
{
list.Add(m);
}
///
/// Given a sequence of MethodInfo and a sequence of types, return the
/// MethodInfo that matches the signature represented by those types.
///
internal static MethodInfo MatchSignature(MethodInfo[] mi, Type[] tp)
{
if (tp == null)
{
return null;
}
int count = tp.Length;
foreach (MethodInfo 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 that represents the matching closed generic.
///
internal static MethodInfo MatchParameters(MethodInfo[] mi, Type[] tp)
{
if (tp == null)
{
return null;
}
int count = tp.Length;
foreach (MethodInfo t in mi)
{
if (!t.IsGenericMethodDefinition)
{
continue;
}
Type[] args = t.GetGenericArguments();
if (args.Length != count)
{
continue;
}
return t.MakeGenericMethod(tp);
}
return null;
}
///
/// 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(MethodInfo[] 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 = (MethodBase[])list.ToArray(typeof(MethodBase));
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)
{
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;
}
TypeCode tc = Type.GetTypeCode(t);
// TODO: Clean up
switch (tc)
{
case TypeCode.Object:
return 1;
case TypeCode.UInt64:
return 10;
case TypeCode.UInt32:
return 11;
case TypeCode.UInt16:
return 12;
case TypeCode.Int64:
return 13;
case TypeCode.Int32:
return 14;
case TypeCode.Int16:
return 15;
case TypeCode.Char:
return 16;
case TypeCode.SByte:
return 17;
case TypeCode.Byte:
return 18;
case TypeCode.Single:
return 20;
case TypeCode.Double:
return 21;
case TypeCode.String:
return 30;
case TypeCode.Boolean:
return 40;
}
if (t.IsArray)
{
Type e = t.GetElementType();
if (e == objectType)
{
return 2500;
}
return 100 + ArgPrecedence(e);
}
return 2000;
}
///
/// Bind the given Python instance and arguments to a particular method
/// overload and return a structure that contains the converted Python
/// instance, converted arguments and the correct method to call.
///
internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw)
{
return Bind(inst, args, kw, null, null);
}
internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info)
{
return Bind(inst, args, kw, info, null);
}
internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, MethodInfo[] methodinfo)
{
// loop to find match, return invoker w/ or /wo error
MethodBase[] _methods = null;
var pynargs = (int)Runtime.PyTuple_Size(args);
var isGeneric = false;
if (info != null)
{
_methods = new MethodBase[1];
_methods.SetValue(info, 0);
}
else
{
_methods = GetMethods();
}
// TODO: Clean up
foreach (MethodBase mi in _methods)
{
if (mi.IsGenericMethod)
{
isGeneric = true;
}
ParameterInfo[] pi = mi.GetParameters();
ArrayList defaultArgList;
bool paramsArray;
if (!MatchesArgumentCount(pynargs, pi, out paramsArray, out defaultArgList)) {
continue;
}
var outs = 0;
var margs = TryConvertArguments(pi, paramsArray, args, pynargs, defaultArgList,
needsResolution: _methods.Length > 1,
outs: out outs);
if (margs == null)
{
continue;
}
object target = null;
if (!mi.IsStatic && inst != IntPtr.Zero)
{
//CLRObject co = (CLRObject)ManagedType.GetManagedObject(inst);
// InvalidCastException: Unable to cast object of type
// 'Python.Runtime.ClassObject' to type 'Python.Runtime.CLRObject'
var co = ManagedType.GetManagedObject(inst) as 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 (co == null)
{
return null;
}
target = co.inst;
}
return new Binding(mi, target, margs, outs);
}
// 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.
if (isGeneric && info == null && methodinfo != null)
{
Type[] types = Runtime.PythonArgsToTypeArray(args, true);
MethodInfo mi = MatchParameters(methodinfo, types);
return Bind(inst, args, kw, mi, null);
}
return null;
}
///
/// Attempts to convert Python argument tuple into an array of managed objects,
/// that can be passed to a method.
///
/// 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
/// A list of default values for omitted parameters
/// true, if overloading resolution is required
/// Returns number of output parameters
/// An array of .NET arguments, that can be passed to a method.
static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray,
IntPtr args, int pyArgCount,
ArrayList defaultArgList,
bool needsResolution,
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++)
{
if (paramIndex >= pyArgCount)
{
if (defaultArgList != null)
{
margs[paramIndex] = defaultArgList[paramIndex - pyArgCount];
}
continue;
}
var parameter = pi[paramIndex];
IntPtr op = (arrayStart == paramIndex)
// map remaining Python arguments to a tuple since
// the managed function accepts it - hopefully :]
? Runtime.PyTuple_GetSlice(args, arrayStart, pyArgCount)
: Runtime.PyTuple_GetItem(args, paramIndex);
bool isOut;
if (!TryConvertArgument(op, parameter.ParameterType, needsResolution, out margs[paramIndex], out isOut))
{
return null;
}
if (arrayStart == paramIndex)
{
// TODO: is this a bug? Should this happen even if the conversion fails?
// GetSlice() creates a new reference but GetItem()
// returns only a borrow reference.
Runtime.XDecref(op);
}
if (parameter.IsOut || isOut)
{
outs++;
}
}
return margs;
}
static bool TryConvertArgument(IntPtr op, Type parameterType, bool needsResolution,
out object arg, out bool isOut)
{
arg = null;
isOut = false;
var clrtype = TryComputeClrArgumentType(parameterType, op, needsResolution: needsResolution);
if (clrtype == null)
{
return false;
}
if (!Converter.ToManaged(op, clrtype, out arg, false))
{
Exceptions.Clear();
return false;
}
isOut = clrtype.IsByRef;
return true;
}
static Type TryComputeClrArgumentType(Type parameterType, IntPtr argument, bool needsResolution)
{
// this logic below handles cases when multiple overloading methods
// are ambiguous, hence comparison between Python and CLR types
// is necessary
Type clrtype = null;
IntPtr pyoptype;
if (needsResolution)
{
// HACK: each overload should be weighted in some way instead
pyoptype = Runtime.PyObject_Type(argument);
Exceptions.Clear();
if (pyoptype != IntPtr.Zero)
{
clrtype = Converter.GetTypeByAlias(pyoptype);
}
Runtime.XDecref(pyoptype);
}
if (clrtype != null)
{
var typematch = false;
if ((parameterType != typeof(object)) && (parameterType != clrtype))
{
IntPtr pytype = Converter.GetPythonTypeByAlias(parameterType);
pyoptype = Runtime.PyObject_Type(argument);
Exceptions.Clear();
if (pyoptype != IntPtr.Zero)
{
if (pytype != pyoptype)
{
typematch = false;
}
else
{
typematch = true;
clrtype = parameterType;
}
}
if (!typematch)
{
// this takes care of enum values
TypeCode argtypecode = Type.GetTypeCode(parameterType);
TypeCode paramtypecode = Type.GetTypeCode(clrtype);
if (argtypecode == paramtypecode)
{
typematch = true;
clrtype = parameterType;
}
}
Runtime.XDecref(pyoptype);
if (!typematch)
{
return null;
}
}
else
{
typematch = true;
clrtype = parameterType;
}
}
else
{
clrtype = parameterType;
}
return clrtype;
}
static bool MatchesArgumentCount(int argumentCount, ParameterInfo[] parameters,
out bool paramsArray,
out ArrayList defaultArgList)
{
defaultArgList = null;
var match = false;
paramsArray = false;
if (argumentCount == parameters.Length)
{
match = true;
} else if (argumentCount < parameters.Length)
{
match = true;
defaultArgList = new ArrayList();
for (var v = argumentCount; v < parameters.Length; v++) {
if (parameters[v].DefaultValue == DBNull.Value) {
match = false;
} else {
defaultArgList.Add(parameters[v].DefaultValue);
}
}
} else if (argumentCount > 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 IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw)
{
return Invoke(inst, args, kw, null, null);
}
internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info)
{
return Invoke(inst, args, kw, info, null);
}
internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, MethodInfo[] methodinfo)
{
Binding binding = Bind(inst, args, kw, info, methodinfo);
object result;
IntPtr ts = IntPtr.Zero;
if (binding == null)
{
var value = "No method matches given arguments";
if (methodinfo != null && methodinfo.Length > 0)
{
value += $" for {methodinfo[0].Name}";
}
Exceptions.SetError(Exceptions.TypeError, value);
return IntPtr.Zero;
}
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 IntPtr.Zero;
}
if (allow_threads)
{
PythonEngine.EndAllowThreads(ts);
}
// If there are out parameters, we return a tuple containing
// the result 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 mi = (MethodInfo)binding.info;
if (binding.outs == 1 && mi.ReturnType == typeof(void))
{
}
if (binding.outs > 0)
{
ParameterInfo[] pi = mi.GetParameters();
int c = pi.Length;
var n = 0;
IntPtr t = Runtime.PyTuple_New(binding.outs + 1);
IntPtr v = Converter.ToPython(result, mi.ReturnType);
Runtime.PyTuple_SetItem(t, n, v);
n++;
for (var i = 0; i < c; i++)
{
Type pt = pi[i].ParameterType;
if (pi[i].IsOut || pt.IsByRef)
{
v = Converter.ToPython(binding.args[i], pt);
Runtime.PyTuple_SetItem(t, n, v);
n++;
}
}
if (binding.outs == 1 && mi.ReturnType == typeof(void))
{
v = Runtime.PyTuple_GetItem(t, 1);
Runtime.XIncref(v);
Runtime.XDecref(t);
return v;
}
return t;
}
return Converter.ToPython(result, mi.ReturnType);
}
}
///
/// Utility class to sort method info by parameter type precedence.
///
internal class MethodSorter : IComparer
{
int IComparer.Compare(object m1, object m2)
{
var me1 = (MethodBase)m1;
var me2 = (MethodBase)m2;
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((MethodBase)m1);
int p2 = MethodBinder.GetPrecedence((MethodBase)m2);
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;
}
}
}