22using System . Collections ;
33using System . Reflection ;
44using System . Text ;
5+ using System . Collections . Generic ;
6+ using System . Linq ;
57
68namespace Python . Runtime
79{
@@ -280,6 +282,22 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
280282 {
281283 // loop to find match, return invoker w/ or /wo error
282284 MethodBase [ ] _methods = null ;
285+
286+ var kwargDict = new Dictionary < string , IntPtr > ( ) ;
287+ if ( kw != IntPtr . Zero )
288+ {
289+ var pynkwargs = ( int ) Runtime . PyDict_Size ( kw ) ;
290+ IntPtr keylist = Runtime . PyDict_Keys ( kw ) ;
291+ IntPtr valueList = Runtime . PyDict_Values ( kw ) ;
292+ for ( int i = 0 ; i < pynkwargs ; ++ i )
293+ {
294+ var keyStr = Runtime . GetManagedString ( Runtime . PyList_GetItem ( keylist , i ) ) ;
295+ kwargDict [ keyStr ] = Runtime . PyList_GetItem ( valueList , i ) ;
296+ }
297+ Runtime . XDecref ( keylist ) ;
298+ Runtime . XDecref ( valueList ) ;
299+ }
300+
283301 var pynargs = ( int ) Runtime . PyTuple_Size ( args ) ;
284302 var isGeneric = false ;
285303 if ( info != null )
@@ -303,11 +321,12 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
303321 ArrayList defaultArgList ;
304322 bool paramsArray ;
305323
306- if ( ! MatchesArgumentCount ( pynargs , pi , out paramsArray , out defaultArgList ) ) {
324+ if ( ! MatchesArgumentCount ( pynargs , pi , kwargDict , out paramsArray , out defaultArgList ) )
325+ {
307326 continue ;
308327 }
309328 var outs = 0 ;
310- var margs = TryConvertArguments ( pi , paramsArray , args , pynargs , defaultArgList ,
329+ var margs = TryConvertArguments ( pi , paramsArray , args , pynargs , kwargDict , defaultArgList ,
311330 needsResolution : _methods . Length > 1 ,
312331 outs : out outs ) ;
313332
@@ -351,19 +370,21 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
351370 }
352371
353372 /// <summary>
354- /// Attempts to convert Python argument tuple into an array of managed objects,
355- /// that can be passed to a method.
373+ /// Attempts to convert Python positional argument tuple and keyword argument table
374+ /// into an array of managed objects, that can be passed to a method.
356375 /// </summary>
357376 /// <param name="pi">Information about expected parameters</param>
358377 /// <param name="paramsArray"><c>true</c>, if the last parameter is a params array.</param>
359378 /// <param name="args">A pointer to the Python argument tuple</param>
360379 /// <param name="pyArgCount">Number of arguments, passed by Python</param>
380+ /// <param name="kwargDict">Dictionary of keyword argument name to python object pointer</param>
361381 /// <param name="defaultArgList">A list of default values for omitted parameters</param>
362382 /// <param name="needsResolution"><c>true</c>, if overloading resolution is required</param>
363383 /// <param name="outs">Returns number of output parameters</param>
364384 /// <returns>An array of .NET arguments, that can be passed to a method.</returns>
365385 static object [ ] TryConvertArguments ( ParameterInfo [ ] pi , bool paramsArray ,
366386 IntPtr args , int pyArgCount ,
387+ Dictionary < string , IntPtr > kwargDict ,
367388 ArrayList defaultArgList ,
368389 bool needsResolution ,
369390 out int outs )
@@ -374,7 +395,10 @@ static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray,
374395
375396 for ( int paramIndex = 0 ; paramIndex < pi . Length ; paramIndex ++ )
376397 {
377- if ( paramIndex >= pyArgCount )
398+ var parameter = pi [ paramIndex ] ;
399+ bool hasNamedParam = kwargDict . ContainsKey ( parameter . Name ) ;
400+
401+ if ( paramIndex >= pyArgCount && ! hasNamedParam )
378402 {
379403 if ( defaultArgList != null )
380404 {
@@ -384,12 +408,19 @@ static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray,
384408 continue ;
385409 }
386410
387- var parameter = pi [ paramIndex ] ;
388- IntPtr op = ( arrayStart == paramIndex )
389- // map remaining Python arguments to a tuple since
390- // the managed function accepts it - hopefully :]
391- ? Runtime . PyTuple_GetSlice ( args , arrayStart , pyArgCount )
392- : Runtime . PyTuple_GetItem ( args , paramIndex ) ;
411+ IntPtr op ;
412+ if ( hasNamedParam )
413+ {
414+ op = kwargDict [ parameter . Name ] ;
415+ }
416+ else
417+ {
418+ op = ( arrayStart == paramIndex )
419+ // map remaining Python arguments to a tuple since
420+ // the managed function accepts it - hopefully :]
421+ ? Runtime . PyTuple_GetSlice ( args , arrayStart , pyArgCount )
422+ : Runtime . PyTuple_GetItem ( args , paramIndex ) ;
423+ }
393424
394425 bool isOut ;
395426 if ( ! TryConvertArgument ( op , parameter . ParameterType , needsResolution , out margs [ paramIndex ] , out isOut ) )
@@ -505,29 +536,49 @@ static Type TryComputeClrArgumentType(Type parameterType, IntPtr argument, bool
505536 return clrtype ;
506537 }
507538
508- static bool MatchesArgumentCount ( int argumentCount , ParameterInfo [ ] parameters ,
539+ static bool MatchesArgumentCount ( int positionalArgumentCount , ParameterInfo [ ] parameters ,
540+ Dictionary < string , IntPtr > kwargDict ,
509541 out bool paramsArray ,
510542 out ArrayList defaultArgList )
511543 {
512544 defaultArgList = null ;
513545 var match = false ;
514546 paramsArray = false ;
515547
516- if ( argumentCount == parameters . Length )
548+ if ( positionalArgumentCount == parameters . Length )
517549 {
518550 match = true ;
519- } else if ( argumentCount < parameters . Length )
551+ }
552+ else if ( positionalArgumentCount < parameters . Length )
520553 {
554+ // every parameter past 'positionalArgumentCount' must have either
555+ // a corresponding keyword argument or a default parameter
521556 match = true ;
522557 defaultArgList = new ArrayList ( ) ;
523- for ( var v = argumentCount ; v < parameters . Length ; v ++ ) {
524- if ( parameters [ v ] . DefaultValue == DBNull . Value ) {
558+ for ( var v = positionalArgumentCount ; v < parameters . Length ; v ++ )
559+ {
560+ if ( kwargDict . ContainsKey ( parameters [ v ] . Name ) )
561+ {
562+ // we have a keyword argument for this parameter,
563+ // no need to check for a default parameter, but put a null
564+ // placeholder in defaultArgList
565+ defaultArgList . Add ( null ) ;
566+ }
567+ else if ( parameters [ v ] . IsOptional )
568+ {
569+ // IsOptional will be true if the parameter has a default value,
570+ // or if the parameter has the [Optional] attribute specified.
571+ // The GetDefaultValue() extension method will return the value
572+ // to be passed in as the parameter value
573+ defaultArgList . Add ( parameters [ v ] . GetDefaultValue ( ) ) ;
574+ }
575+ else
576+ {
525577 match = false ;
526- } else {
527- defaultArgList . Add ( parameters [ v ] . DefaultValue ) ;
528578 }
529579 }
530- } else if ( argumentCount > parameters . Length && parameters . Length > 0 &&
580+ }
581+ else if ( positionalArgumentCount > parameters . Length && parameters . Length > 0 &&
531582 Attribute . IsDefined ( parameters [ parameters . Length - 1 ] , typeof ( ParamArrayAttribute ) ) )
532583 {
533584 // This is a `foo(params object[] bar)` style method
@@ -722,4 +773,33 @@ internal Binding(MethodBase info, object inst, object[] args, int outs)
722773 this . outs = outs ;
723774 }
724775 }
776+
777+
778+ static internal class ParameterInfoExtensions
779+ {
780+ public static object GetDefaultValue ( this ParameterInfo parameterInfo )
781+ {
782+ // parameterInfo.HasDefaultValue is preferable but doesn't exist in .NET 4.0
783+ bool hasDefaultValue = ( parameterInfo . Attributes & ParameterAttributes . HasDefault ) ==
784+ ParameterAttributes . HasDefault ;
785+
786+ if ( hasDefaultValue )
787+ {
788+ return parameterInfo . DefaultValue ;
789+ }
790+ else
791+ {
792+ // [OptionalAttribute] was specified for the parameter.
793+ // See https://stackoverflow.com/questions/3416216/optionalattribute-parameters-default-value
794+ // for rules on determining the value to pass to the parameter
795+ var type = parameterInfo . ParameterType ;
796+ if ( type == typeof ( object ) )
797+ return Type . Missing ;
798+ else if ( type . IsValueType )
799+ return Activator . CreateInstance ( type ) ;
800+ else
801+ return null ;
802+ }
803+ }
804+ }
725805}
0 commit comments