diff --git a/AUTHORS.md b/AUTHORS.md
index fe2d2b172..53108463e 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -43,6 +43,7 @@
- Sean Freitag ([@cowboygneox](https://github.com/cowboygneox))
- Serge Weinstock ([@sweinst](https://github.com/sweinst))
- Simon Mourier ([@smourier](https://github.com/smourier))
+- Victor Milovanov ([@lostmsu](https://github.com/lostmsu))
- Viktoria Kovescses ([@vkovec](https://github.com/vkovec))
- Ville M. Vainio ([@vivainio](https://github.com/vivainio))
- Virgil Dupras ([@hsoft](https://github.com/hsoft))
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a3816cd0c..17c1780ee 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -28,6 +28,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
- Reattach python exception traceback information (#545)
- PythonEngine.Intialize will now call `Py_InitializeEx` with a default value of 0, so signals will not be configured by default on embedding. This is different from the previous behaviour, where `Py_Initialize` was called instead, which sets initSigs to 1. ([#449][i449])
+- Refactored MethodBinder.Bind in preparation to make it extensible (#829)
### Fixed
diff --git a/src/runtime/methodbinder.cs b/src/runtime/methodbinder.cs
index 5e800c36f..7471d5d7c 100644
--- a/src/runtime/methodbinder.cs
+++ b/src/runtime/methodbinder.cs
@@ -280,9 +280,7 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
// loop to find match, return invoker w/ or /wo error
MethodBase[] _methods = null;
var pynargs = (int)Runtime.PyTuple_Size(args);
- object arg;
var isGeneric = false;
- ArrayList defaultArgList = null;
if (info != null)
{
_methods = new MethodBase[1];
@@ -292,7 +290,7 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
{
_methods = GetMethods();
}
- Type clrtype;
+
// TODO: Clean up
foreach (MethodBase mi in _methods)
{
@@ -301,192 +299,242 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
isGeneric = true;
}
ParameterInfo[] pi = mi.GetParameters();
- var clrnargs = pi.Length;
- var match = false;
- var arrayStart = -1;
+ 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 (pynargs == clrnargs)
+ if (margs == null)
{
- match = true;
+ continue;
}
- else if (pynargs < clrnargs)
+
+ object target = null;
+ if (!mi.IsStatic && inst != IntPtr.Zero)
{
- match = true;
- defaultArgList = new ArrayList();
- for (var v = pynargs; v < clrnargs; v++)
+ //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)
{
- if (pi[v].DefaultValue == DBNull.Value)
- {
- match = false;
- }
- else
- {
- defaultArgList.Add(pi[v].DefaultValue);
- }
+ return null;
}
+ target = co.inst;
}
- else if (pynargs > clrnargs && clrnargs > 0 &&
- Attribute.IsDefined(pi[clrnargs - 1], typeof(ParamArrayAttribute)))
+
+ 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)
{
- // This is a `foo(params object[] bar)` style method
- match = true;
- arrayStart = clrnargs - 1;
+ if (defaultArgList != null)
+ {
+ margs[paramIndex] = defaultArgList[paramIndex - pyArgCount];
+ }
+
+ continue;
}
- if (match)
+ 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)
{
- var margs = new object[clrnargs];
+ clrtype = Converter.GetTypeByAlias(pyoptype);
+ }
+ Runtime.XDecref(pyoptype);
+ }
- for (int n = 0; n < clrnargs; n++)
+ 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)
{
- IntPtr op;
- if (n < pynargs)
+ if (pytype != pyoptype)
{
- if (arrayStart == n)
- {
- // map remaining Python arguments to a tuple since
- // the managed function accepts it - hopefully :]
- op = Runtime.PyTuple_GetSlice(args, arrayStart, pynargs);
- }
- else
- {
- op = Runtime.PyTuple_GetItem(args, n);
- }
-
- // this logic below handles cases when multiple overloading methods
- // are ambiguous, hence comparison between Python and CLR types
- // is necessary
- clrtype = null;
- IntPtr pyoptype;
- if (_methods.Length > 1)
- {
- pyoptype = IntPtr.Zero;
- pyoptype = Runtime.PyObject_Type(op);
- Exceptions.Clear();
- if (pyoptype != IntPtr.Zero)
- {
- clrtype = Converter.GetTypeByAlias(pyoptype);
- }
- Runtime.XDecref(pyoptype);
- }
-
-
- if (clrtype != null)
- {
- var typematch = false;
- if ((pi[n].ParameterType != typeof(object)) && (pi[n].ParameterType != clrtype))
- {
- IntPtr pytype = Converter.GetPythonTypeByAlias(pi[n].ParameterType);
- pyoptype = Runtime.PyObject_Type(op);
- Exceptions.Clear();
- if (pyoptype != IntPtr.Zero)
- {
- if (pytype != pyoptype)
- {
- typematch = false;
- }
- else
- {
- typematch = true;
- clrtype = pi[n].ParameterType;
- }
- }
- if (!typematch)
- {
- // this takes care of enum values
- TypeCode argtypecode = Type.GetTypeCode(pi[n].ParameterType);
- TypeCode paramtypecode = Type.GetTypeCode(clrtype);
- if (argtypecode == paramtypecode)
- {
- typematch = true;
- clrtype = pi[n].ParameterType;
- }
- }
- Runtime.XDecref(pyoptype);
- if (!typematch)
- {
- margs = null;
- break;
- }
- }
- else
- {
- typematch = true;
- clrtype = pi[n].ParameterType;
- }
- }
- else
- {
- clrtype = pi[n].ParameterType;
- }
-
- if (pi[n].IsOut || clrtype.IsByRef)
- {
- outs++;
- }
-
- if (!Converter.ToManaged(op, clrtype, out arg, false))
- {
- Exceptions.Clear();
- margs = null;
- break;
- }
- if (arrayStart == n)
- {
- // GetSlice() creates a new reference but GetItem()
- // returns only a borrow reference.
- Runtime.XDecref(op);
- }
- margs[n] = arg;
+ typematch = false;
}
else
{
- if (defaultArgList != null)
- {
- margs[n] = defaultArgList[n - pynargs];
- }
+ typematch = true;
+ clrtype = parameterType;
}
}
-
- if (margs == null)
- {
- continue;
- }
-
- object target = null;
- if (!mi.IsStatic && inst != IntPtr.Zero)
+ if (!typematch)
{
- //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)
+ // this takes care of enum values
+ TypeCode argtypecode = Type.GetTypeCode(parameterType);
+ TypeCode paramtypecode = Type.GetTypeCode(clrtype);
+ if (argtypecode == paramtypecode)
{
- return null;
+ typematch = true;
+ clrtype = parameterType;
}
- target = co.inst;
}
-
- return new Binding(mi, target, margs, outs);
+ Runtime.XDecref(pyoptype);
+ if (!typematch)
+ {
+ return null;
+ }
+ }
+ else
+ {
+ typematch = true;
+ clrtype = parameterType;
}
}
- // 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)
+ else
{
- Type[] types = Runtime.PythonArgsToTypeArray(args, true);
- MethodInfo mi = MatchParameters(methodinfo, types);
- return Bind(inst, args, kw, mi, null);
+ clrtype = parameterType;
}
- return null;
+
+ 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)