2
2
using System . Collections ;
3
3
using System . Reflection ;
4
4
using System . Text ;
5
+ using System . Collections . Generic ;
6
+ using System . Linq ;
5
7
6
8
namespace Python . Runtime
7
9
{
@@ -292,6 +294,22 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
292
294
{
293
295
// loop to find match, return invoker w/ or /wo error
294
296
MethodBase [ ] _methods = null ;
297
+
298
+ var kwargDict = new Dictionary < string , IntPtr > ( ) ;
299
+ if ( kw != IntPtr . Zero )
300
+ {
301
+ var pynkwargs = ( int ) Runtime . PyDict_Size ( kw ) ;
302
+ IntPtr keylist = Runtime . PyDict_Keys ( kw ) ;
303
+ IntPtr valueList = Runtime . PyDict_Values ( kw ) ;
304
+ for ( int i = 0 ; i < pynkwargs ; ++ i )
305
+ {
306
+ var keyStr = Runtime . GetManagedString ( Runtime . PyList_GetItem ( keylist , i ) ) ;
307
+ kwargDict [ keyStr ] = Runtime . PyList_GetItem ( valueList , i ) ;
308
+ }
309
+ Runtime . XDecref ( keylist ) ;
310
+ Runtime . XDecref ( valueList ) ;
311
+ }
312
+
295
313
var pynargs = ( int ) Runtime . PyTuple_Size ( args ) ;
296
314
var isGeneric = false ;
297
315
if ( info != null )
@@ -315,11 +333,12 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
315
333
ArrayList defaultArgList ;
316
334
bool paramsArray ;
317
335
318
- if ( ! MatchesArgumentCount ( pynargs , pi , out paramsArray , out defaultArgList ) ) {
336
+ if ( ! MatchesArgumentCount ( pynargs , pi , kwargDict , out paramsArray , out defaultArgList ) )
337
+ {
319
338
continue ;
320
339
}
321
340
var outs = 0 ;
322
- var margs = TryConvertArguments ( pi , paramsArray , args , pynargs , defaultArgList ,
341
+ var margs = TryConvertArguments ( pi , paramsArray , args , pynargs , kwargDict , defaultArgList ,
323
342
needsResolution : _methods . Length > 1 ,
324
343
outs : out outs ) ;
325
344
@@ -363,19 +382,21 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth
363
382
}
364
383
365
384
/// <summary>
366
- /// Attempts to convert Python argument tuple into an array of managed objects,
367
- /// that can be passed to a method.
385
+ /// Attempts to convert Python positional argument tuple and keyword argument table
386
+ /// into an array of managed objects, that can be passed to a method.
368
387
/// </summary>
369
388
/// <param name="pi">Information about expected parameters</param>
370
389
/// <param name="paramsArray"><c>true</c>, if the last parameter is a params array.</param>
371
390
/// <param name="args">A pointer to the Python argument tuple</param>
372
391
/// <param name="pyArgCount">Number of arguments, passed by Python</param>
392
+ /// <param name="kwargDict">Dictionary of keyword argument name to python object pointer</param>
373
393
/// <param name="defaultArgList">A list of default values for omitted parameters</param>
374
394
/// <param name="needsResolution"><c>true</c>, if overloading resolution is required</param>
375
395
/// <param name="outs">Returns number of output parameters</param>
376
396
/// <returns>An array of .NET arguments, that can be passed to a method.</returns>
377
397
static object [ ] TryConvertArguments ( ParameterInfo [ ] pi , bool paramsArray ,
378
398
IntPtr args , int pyArgCount ,
399
+ Dictionary < string , IntPtr > kwargDict ,
379
400
ArrayList defaultArgList ,
380
401
bool needsResolution ,
381
402
out int outs )
@@ -386,7 +407,10 @@ static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray,
386
407
387
408
for ( int paramIndex = 0 ; paramIndex < pi . Length ; paramIndex ++ )
388
409
{
389
- if ( paramIndex >= pyArgCount )
410
+ var parameter = pi [ paramIndex ] ;
411
+ bool hasNamedParam = kwargDict . ContainsKey ( parameter . Name ) ;
412
+
413
+ if ( paramIndex >= pyArgCount && ! hasNamedParam )
390
414
{
391
415
if ( defaultArgList != null )
392
416
{
@@ -396,12 +420,19 @@ static object[] TryConvertArguments(ParameterInfo[] pi, bool paramsArray,
396
420
continue ;
397
421
}
398
422
399
- var parameter = pi [ paramIndex ] ;
400
- IntPtr op = ( arrayStart == paramIndex )
401
- // map remaining Python arguments to a tuple since
402
- // the managed function accepts it - hopefully :]
403
- ? Runtime . PyTuple_GetSlice ( args , arrayStart , pyArgCount )
404
- : Runtime . PyTuple_GetItem ( args , paramIndex ) ;
423
+ IntPtr op ;
424
+ if ( hasNamedParam )
425
+ {
426
+ op = kwargDict [ parameter . Name ] ;
427
+ }
428
+ else
429
+ {
430
+ op = ( arrayStart == paramIndex )
431
+ // map remaining Python arguments to a tuple since
432
+ // the managed function accepts it - hopefully :]
433
+ ? Runtime . PyTuple_GetSlice ( args , arrayStart , pyArgCount )
434
+ : Runtime . PyTuple_GetItem ( args , paramIndex ) ;
435
+ }
405
436
406
437
bool isOut ;
407
438
if ( ! TryConvertArgument ( op , parameter . ParameterType , needsResolution , out margs [ paramIndex ] , out isOut ) )
@@ -537,29 +568,49 @@ static Type TryComputeClrArgumentType(Type parameterType, IntPtr argument, bool
537
568
return clrtype ;
538
569
}
539
570
540
- static bool MatchesArgumentCount ( int argumentCount , ParameterInfo [ ] parameters ,
571
+ static bool MatchesArgumentCount ( int positionalArgumentCount , ParameterInfo [ ] parameters ,
572
+ Dictionary < string , IntPtr > kwargDict ,
541
573
out bool paramsArray ,
542
574
out ArrayList defaultArgList )
543
575
{
544
576
defaultArgList = null ;
545
577
var match = false ;
546
578
paramsArray = false ;
547
579
548
- if ( argumentCount == parameters . Length )
580
+ if ( positionalArgumentCount == parameters . Length )
549
581
{
550
582
match = true ;
551
- } else if ( argumentCount < parameters . Length )
583
+ }
584
+ else if ( positionalArgumentCount < parameters . Length )
552
585
{
586
+ // every parameter past 'positionalArgumentCount' must have either
587
+ // a corresponding keyword argument or a default parameter
553
588
match = true ;
554
589
defaultArgList = new ArrayList ( ) ;
555
- for ( var v = argumentCount ; v < parameters . Length ; v ++ ) {
556
- if ( parameters [ v ] . DefaultValue == DBNull . Value ) {
590
+ for ( var v = positionalArgumentCount ; v < parameters . Length ; v ++ )
591
+ {
592
+ if ( kwargDict . ContainsKey ( parameters [ v ] . Name ) )
593
+ {
594
+ // we have a keyword argument for this parameter,
595
+ // no need to check for a default parameter, but put a null
596
+ // placeholder in defaultArgList
597
+ defaultArgList . Add ( null ) ;
598
+ }
599
+ else if ( parameters [ v ] . IsOptional )
600
+ {
601
+ // IsOptional will be true if the parameter has a default value,
602
+ // or if the parameter has the [Optional] attribute specified.
603
+ // The GetDefaultValue() extension method will return the value
604
+ // to be passed in as the parameter value
605
+ defaultArgList . Add ( parameters [ v ] . GetDefaultValue ( ) ) ;
606
+ }
607
+ else
608
+ {
557
609
match = false ;
558
- } else {
559
- defaultArgList . Add ( parameters [ v ] . DefaultValue ) ;
560
610
}
561
611
}
562
- } else if ( argumentCount > parameters . Length && parameters . Length > 0 &&
612
+ }
613
+ else if ( positionalArgumentCount > parameters . Length && parameters . Length > 0 &&
563
614
Attribute . IsDefined ( parameters [ parameters . Length - 1 ] , typeof ( ParamArrayAttribute ) ) )
564
615
{
565
616
// This is a `foo(params object[] bar)` style method
@@ -754,4 +805,33 @@ internal Binding(MethodBase info, object inst, object[] args, int outs)
754
805
this . outs = outs ;
755
806
}
756
807
}
808
+
809
+
810
+ static internal class ParameterInfoExtensions
811
+ {
812
+ public static object GetDefaultValue ( this ParameterInfo parameterInfo )
813
+ {
814
+ // parameterInfo.HasDefaultValue is preferable but doesn't exist in .NET 4.0
815
+ bool hasDefaultValue = ( parameterInfo . Attributes & ParameterAttributes . HasDefault ) ==
816
+ ParameterAttributes . HasDefault ;
817
+
818
+ if ( hasDefaultValue )
819
+ {
820
+ return parameterInfo . DefaultValue ;
821
+ }
822
+ else
823
+ {
824
+ // [OptionalAttribute] was specified for the parameter.
825
+ // See https://stackoverflow.com/questions/3416216/optionalattribute-parameters-default-value
826
+ // for rules on determining the value to pass to the parameter
827
+ var type = parameterInfo . ParameterType ;
828
+ if ( type == typeof ( object ) )
829
+ return Type . Missing ;
830
+ else if ( type . IsValueType )
831
+ return Activator . CreateInstance ( type ) ;
832
+ else
833
+ return null ;
834
+ }
835
+ }
836
+ }
757
837
}
0 commit comments