/// <summary> /// Bind the given Python instance and arguments to a particular method /// overload in <see cref="list"/> 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. /// </summary> /// <param name="inst">The Python target of the method invocation.</param> /// <param name="args">The Python arguments.</param> /// <param name="kw">The Python keyword arguments.</param> /// <param name="info">If not null, only bind to that method.</param> /// <param name="methodinfo">If not null, additionally attempt to bind to the generic methods in this array by inferring generic type parameters.</param> /// <returns>A Binding if successful. Otherwise null.</returns> internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, MethodInfo[] methodinfo) { // loop to find match, return invoker w/ or w/o error MethodBase[] _methods = null; var kwargDict = new Dictionary <string, IntPtr>(); if (kw != IntPtr.Zero) { var pynkwargs = (int)Runtime.PyDict_Size(kw); IntPtr keylist = Runtime.PyDict_Keys(kw); IntPtr valueList = Runtime.PyDict_Values(kw); for (int i = 0; i < pynkwargs; ++i) { var keyStr = Runtime.GetManagedString(Runtime.PyList_GetItem(new BorrowedReference(keylist), i)); kwargDict[keyStr] = Runtime.PyList_GetItem(new BorrowedReference(valueList), i).DangerousGetAddress(); } Runtime.XDecref(keylist); Runtime.XDecref(valueList); } var pynargs = (int)Runtime.PyTuple_Size(args); var isGeneric = false; if (info != null) { _methods = new MethodBase[1]; _methods.SetValue(info, 0); } else { _methods = GetMethods(); } var argMatchedMethods = new List <MatchedMethod>(_methods.Length); var mismatchedMethods = new List <MismatchedMethod>(); // TODO: Clean up foreach (MethodBase mi in _methods) { if (mi.IsGenericMethod) { isGeneric = true; } ParameterInfo[] pi = mi.GetParameters(); ArrayList defaultArgList; bool paramsArray; int kwargsMatched; int defaultsNeeded; 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 && OperatorMethod.IsReverse((MethodInfo)mi); // 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 paramsArray, out defaultArgList, out kwargsMatched, out 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(); } int outs; var margs = TryConvertArguments(pi, paramsArray, args, pynargs, kwargDict, defaultArgList, outs: out outs); if (margs == null) { var mismatchCause = PythonException.FetchCurrent(); mismatchedMethods.Add(new MismatchedMethod(mismatchCause, mi)); continue; } if (isOperator) { if (inst != IntPtr.Zero) { 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 StringBuilder 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 != 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) { Exceptions.SetError(Exceptions.TypeError, "Invoked a non-static method with an invalid instance"); return(null); } target = co.inst; } return(new Binding(mi, target, margs, outs)); } else if (isGeneric && info == null && methodinfo != null) { // 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 mi = MatchParameters(methodinfo, types); if (mi != null) { return(Bind(inst, args, kw, mi, null)); } } if (mismatchedMethods.Count > 0) { var aggregateException = GetAggregateException(mismatchedMethods); Exceptions.SetError(aggregateException); } return(null); }
internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, MethodInfo[] methodinfo) { // Relevant function variables used post conversion var isGeneric = false; Binding bindingUsingImplicitConversion = null; // If we have KWArgs create dictionary and collect them Dictionary <string, IntPtr> kwArgDict = null; if (kw != IntPtr.Zero) { var pyKwArgsCount = (int)Runtime.PyDict_Size(kw); kwArgDict = new Dictionary <string, IntPtr>(pyKwArgsCount); IntPtr keylist = Runtime.PyDict_Keys(kw); IntPtr valueList = Runtime.PyDict_Values(kw); for (int i = 0; i < pyKwArgsCount; ++i) { var keyStr = Runtime.GetManagedString(Runtime.PyList_GetItem(new BorrowedReference(keylist), i)); kwArgDict[keyStr] = Runtime.PyList_GetItem(new BorrowedReference(valueList), i).DangerousGetAddress(); } Runtime.XDecref(keylist); Runtime.XDecref(valueList); } // Fetch our methods we are going to attempt to match and bind too. var methods = info == null?GetMethods() : new List <MethodInformation>(1) { new MethodInformation(info, info.GetParameters()) }; foreach (var methodInformation in methods) { // Relevant method variables var mi = methodInformation.MethodBase; var pi = methodInformation.ParameterInfo; isGeneric = mi.IsGenericMethod; int pyArgCount = (int)Runtime.PyTuple_Size(args); // Special case for operators 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 && pyArgCount == pi.Length - 1; bool isReverse = isOperator && OperatorMethod.IsReverse((MethodInfo)mi); // Only cast if isOperator. if (isReverse && OperatorMethod.IsComparisonOp((MethodInfo)mi)) { continue; // Comparison operators in Python have no reverse mode. } // 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(); } // Must be done after IsOperator section int clrArgCount = pi.Length; if (CheckMethodArgumentsMatch(clrArgCount, pyArgCount, kwArgDict, pi, out bool paramsArray, out ArrayList defaultArgList)) { var outs = 0; var margs = new object[clrArgCount]; int paramsArrayIndex = paramsArray ? pi.Length - 1 : -1; // -1 indicates no paramsArray var usedImplicitConversion = false; // Conversion loop for each parameter for (int paramIndex = 0; paramIndex < clrArgCount; paramIndex++) { IntPtr op = IntPtr.Zero; // Python object to be converted; not yet set var parameter = pi[paramIndex]; // Clr parameter we are targeting object arg; // Python -> Clr argument // Check our KWargs for this parameter bool hasNamedParam = kwArgDict == null ? false : kwArgDict.TryGetValue(parameter.Name, out op); bool isNewReference = false; // Check if we are going to use default if (paramIndex >= pyArgCount && !(hasNamedParam || (paramsArray && paramIndex == paramsArrayIndex))) { if (defaultArgList != null) { margs[paramIndex] = defaultArgList[paramIndex - pyArgCount]; } continue; } // At this point, if op is IntPtr.Zero we don't have a KWArg and are not using default if (op == IntPtr.Zero) { // If we have reached the paramIndex if (paramsArrayIndex == paramIndex) { op = HandleParamsArray(args, paramsArrayIndex, pyArgCount, out isNewReference); } else { op = Runtime.PyTuple_GetItem(args, paramIndex); } } // 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 (methods.Count > 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 ((parameter.ParameterType != typeof(object)) && (parameter.ParameterType != clrtype)) { IntPtr pytype = Converter.GetPythonTypeByAlias(parameter.ParameterType); pyoptype = Runtime.PyObject_Type(op); Exceptions.Clear(); if (pyoptype != IntPtr.Zero) { if (pytype != pyoptype) { typematch = false; } else { typematch = true; clrtype = parameter.ParameterType; } } if (!typematch) { // this takes care of nullables var underlyingType = Nullable.GetUnderlyingType(parameter.ParameterType); if (underlyingType == null) { underlyingType = parameter.ParameterType; } // this takes care of enum values TypeCode argtypecode = Type.GetTypeCode(underlyingType); TypeCode paramtypecode = Type.GetTypeCode(clrtype); if (argtypecode == paramtypecode) { typematch = true; clrtype = parameter.ParameterType; } // accepts non-decimal numbers in decimal parameters if (underlyingType == typeof(decimal)) { clrtype = parameter.ParameterType; typematch = Converter.ToManaged(op, clrtype, out arg, false); } // this takes care of implicit conversions var opImplicit = parameter.ParameterType.GetMethod("op_Implicit", new[] { clrtype }); if (opImplicit != null) { usedImplicitConversion = typematch = opImplicit.ReturnType == parameter.ParameterType; clrtype = parameter.ParameterType; } } Runtime.XDecref(pyoptype); if (!typematch) { margs = null; break; } } else { clrtype = parameter.ParameterType; } } else { clrtype = parameter.ParameterType; } if (parameter.IsOut || clrtype.IsByRef) { outs++; } if (!Converter.ToManaged(op, clrtype, out arg, false)) { Exceptions.Clear(); margs = null; break; } if (isNewReference) { // 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); } margs[paramIndex] = arg; } if (margs == null) { continue; } if (isOperator) { if (inst != IntPtr.Zero) { if (ManagedType.GetManagedObject(inst) is CLRObject co) { bool isUnary = pyArgCount == 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; } } } 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; } var binding = new Binding(mi, target, margs, outs); if (usedImplicitConversion) { // lets just keep the first binding using implicit conversion // this is to respect method order/precedence if (bindingUsingImplicitConversion == null) { // in this case we will not return the binding yet in case there is a match // which does not use implicit conversions, which will return directly bindingUsingImplicitConversion = binding; } } else { return(binding); } } } // if we generated a binding using implicit conversion return it if (bindingUsingImplicitConversion != null) { return(bindingUsingImplicitConversion); } // 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); }