@@ -456,6 +456,9 @@ internal Binding Bind(BorrowedReference inst, BorrowedReference args, BorrowedRe
456456 var methods = info == null ? GetMethods ( )
457457 : new List < MethodInformation > ( 1 ) { new MethodInformation ( info , true ) } ;
458458
459+ var matches = new List < MatchedMethod > ( ) ;
460+ var matchesUsingImplicitConversion = new List < MatchedMethod > ( ) ;
461+
459462 for ( var i = 0 ; i < methods . Count ; i ++ )
460463 {
461464 var methodInformation = methods [ i ] ;
@@ -504,6 +507,8 @@ internal Binding Bind(BorrowedReference inst, BorrowedReference args, BorrowedRe
504507
505508 int paramsArrayIndex = paramsArray ? pi . Length - 1 : - 1 ; // -1 indicates no paramsArray
506509 var usedImplicitConversion = false ;
510+ var kwargsMatched = 0 ;
511+ var defaultsNeeded = 0 ;
507512
508513 // Conversion loop for each parameter
509514 for ( int paramIndex = 0 ; paramIndex < clrArgCount ; paramIndex ++ )
@@ -513,25 +518,34 @@ internal Binding Bind(BorrowedReference inst, BorrowedReference args, BorrowedRe
513518 var parameter = pi [ paramIndex ] ; // Clr parameter we are targeting
514519 object arg ; // Python -> Clr argument
515520
521+ var hasNamedParam = kwArgDict == null ? false : kwArgDict . TryGetValue ( paramNames [ paramIndex ] , out tempPyObject ) ;
522+
523+ // Check positional arguments first and then check for named arguments and optional values
524+ if ( paramIndex >= pyArgCount )
525+ {
526+ // All positional arguments have been used:
516527 // Check our KWargs for this parameter
517- bool hasNamedParam = kwArgDict == null ? false : kwArgDict . TryGetValue ( paramNames [ paramIndex ] , out tempPyObject ) ;
528+ if ( hasNamedParam )
529+ {
530+ kwargsMatched ++ ;
518531 if ( tempPyObject != null )
519532 {
520533 op = tempPyObject ;
521534 }
522-
523- NewReference tempObject = default ;
524-
525- // Check if we are going to use default
526- if ( paramIndex >= pyArgCount && ! ( hasNamedParam || ( paramsArray && paramIndex == paramsArrayIndex ) ) )
535+ }
536+ else if ( parameter . IsOptional && ! ( hasNamedParam || ( paramsArray && paramIndex == paramsArrayIndex ) ) )
527537 {
538+ defaultsNeeded ++ ;
528539 if ( defaultArgList != null )
529540 {
530541 margs [ paramIndex ] = defaultArgList [ paramIndex - pyArgCount ] ;
531542 }
532543
533544 continue ;
534545 }
546+ }
547+
548+ NewReference tempObject = default ;
535549
536550 // At this point, if op is IntPtr.Zero we don't have a KWArg and are not using default
537551 if ( op == null )
@@ -601,9 +615,7 @@ internal Binding Bind(BorrowedReference inst, BorrowedReference args, BorrowedRe
601615 typematch = true ;
602616 clrtype = parameter . ParameterType ;
603617 }
604- // lets just keep the first binding using implicit conversion
605- // this is to respect method order/precedence
606- else if ( bindingUsingImplicitConversion == null )
618+ else
607619 {
608620 // accepts non-decimal numbers in decimal parameters
609621 if ( underlyingType == typeof ( decimal ) )
@@ -687,58 +699,59 @@ internal Binding Bind(BorrowedReference inst, BorrowedReference args, BorrowedRe
687699 }
688700 }
689701
690- object target = null ;
702+ var match = new MatchedMethod ( kwargsMatched , margs , outs , mi ) ;
703+ if ( usedImplicitConversion )
704+ {
705+ matchesUsingImplicitConversion . Add ( match ) ;
706+ }
707+ else
708+ {
709+ matches . Add ( match ) ;
710+ }
711+ }
712+ }
713+
714+ if ( matches . Count > 0 || matchesUsingImplicitConversion . Count > 0 )
715+ {
716+ // We favor matches that do not use implicit conversion
717+ var matchesTouse = matches . Count > 0 ? matches : matchesUsingImplicitConversion ;
718+
719+ // The best match would be the one with the most named arguments matched
720+ var bestMatch = matchesTouse . MaxBy ( x => x . KwargsMatched ) ;
721+ var margs = bestMatch . ManagedArgs ;
722+ var outs = bestMatch . Outs ;
723+ var mi = bestMatch . Method ;
724+
725+ object ? target = null ;
691726 if ( ! mi . IsStatic && inst != null )
692727 {
693728 //CLRObject co = (CLRObject)ManagedType.GetManagedObject(inst);
694729 // InvalidCastException: Unable to cast object of type
695730 // 'Python.Runtime.ClassObject' to type 'Python.Runtime.CLRObject'
696- var co = ManagedType . GetManagedObject ( inst ) as CLRObject ;
697731
698732 // Sanity check: this ensures a graceful exit if someone does
699733 // something intentionally wrong like call a non-static method
700734 // on the class rather than on an instance of the class.
701735 // XXX maybe better to do this before all the other rigmarole.
702- if ( co == null )
736+ if ( ManagedType . GetManagedObject ( inst ) is CLRObject co )
737+ {
738+ target = co . inst ;
739+ }
740+ else
703741 {
742+ Exceptions . SetError ( Exceptions . TypeError , "Invoked a non-static method with an invalid instance" ) ;
704743 return null ;
705744 }
706- target = co . inst ;
707745 }
708746
709747 // If this match is generic we need to resolve it with our types.
710748 // Store this generic match to be used if no others match
711749 if ( mi . IsGenericMethod )
712750 {
713751 mi = ResolveGenericMethod ( ( MethodInfo ) mi , margs ) ;
714- genericBinding = new Binding ( mi , target , margs , outs ) ;
715- continue ;
716- }
717-
718- var binding = new Binding ( mi , target , margs , outs ) ;
719- if ( usedImplicitConversion )
720- {
721- // in this case we will not return the binding yet in case there is a match
722- // which does not use implicit conversions, which will return directly
723- bindingUsingImplicitConversion = binding ;
724- }
725- else
726- {
727- return binding ;
728- }
729- }
730- }
731-
732- // if we generated a binding using implicit conversion return it
733- if ( bindingUsingImplicitConversion != null )
734- {
735- return bindingUsingImplicitConversion ;
736752 }
737753
738- // if we generated a generic binding, return it
739- if ( genericBinding != null )
740- {
741- return genericBinding ;
754+ return new Binding ( mi , target , margs , outs ) ;
742755 }
743756
744757 return null ;
@@ -1063,6 +1076,23 @@ public int Compare(MethodInformation x, MethodInformation y)
10631076 return 0 ;
10641077 }
10651078 }
1079+
1080+ private readonly struct MatchedMethod
1081+ {
1082+ public int KwargsMatched { get ; }
1083+ public object ? [ ] ManagedArgs { get ; }
1084+ public int Outs { get ; }
1085+ public MethodBase Method { get ; }
1086+
1087+ public MatchedMethod ( int kwargsMatched , object ? [ ] margs , int outs , MethodBase mb )
1088+ {
1089+ KwargsMatched = kwargsMatched ;
1090+ ManagedArgs = margs ;
1091+ Outs = outs ;
1092+ Method = mb ;
1093+ }
1094+ }
1095+
10661096 protected static void AppendArgumentTypes ( StringBuilder to , BorrowedReference args )
10671097 {
10681098 long argCount = Runtime . PyTuple_Size ( args ) ;
0 commit comments