@@ -1042,6 +1042,14 @@ internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference a
10421042 value . Append ( ". " ) . Append ( overloads ) ;
10431043 }
10441044
1045+ // After the overloads block: consumers that extract the hint from
1046+ // its marker onwards must keep this line too.
1047+ var mismatch = DiagnoseClosestOverloadMismatch ( candidates , args , kw ) ;
1048+ if ( mismatch . Length > 0 )
1049+ {
1050+ value . Append ( '\n ' ) . Append ( mismatch ) ;
1051+ }
1052+
10451053 Exceptions . RaiseTypeError ( value . ToString ( ) ) ;
10461054 }
10471055
@@ -1216,6 +1224,228 @@ public MatchedMethod(int kwargsMatched, object?[] margs, int outs, MethodInforma
12161224 }
12171225 }
12181226
1227+ /// <summary>
1228+ /// One-line diagnosis of the first argument failing to match the nearest
1229+ /// overload (most leading convertible arguments), e.g. "Argument mismatch:
1230+ /// argument 3 ('asynchronous') expected bool, got str." Empty when nothing
1231+ /// conclusive (e.g. pure arity mismatch). Never throws, never leaves a
1232+ /// Python error pending.
1233+ /// </summary>
1234+ private static string DiagnoseClosestOverloadMismatch ( IEnumerable < MethodBase > candidates , BorrowedReference args , BorrowedReference kw )
1235+ {
1236+ try
1237+ {
1238+ if ( candidates == null )
1239+ {
1240+ return string . Empty ;
1241+ }
1242+
1243+ var pyArgCount = args == null ? 0 : ( int ) Runtime . PyTuple_Size ( args ) ;
1244+
1245+ // Strong references: the values must outlive the candidate probing.
1246+ List < KeyValuePair < string , PyObject > > kwargs = null ;
1247+ if ( kw != null && Runtime . PyDict_Size ( kw ) > 0 )
1248+ {
1249+ kwargs = new List < KeyValuePair < string , PyObject > > ( ) ;
1250+ using var keyList = Runtime . PyDict_Keys ( kw ) ;
1251+ using var valueList = Runtime . PyDict_Values ( kw ) ;
1252+ var kwCount = ( int ) Runtime . PyList_Size ( keyList . Borrow ( ) ) ;
1253+ for ( var i = 0 ; i < kwCount ; i ++ )
1254+ {
1255+ var name = Runtime . GetManagedString ( Runtime . PyList_GetItem ( keyList . Borrow ( ) , i ) ) ;
1256+ if ( name != null )
1257+ {
1258+ kwargs . Add ( new KeyValuePair < string , PyObject > (
1259+ name , new PyObject ( Runtime . PyList_GetItem ( valueList . Borrow ( ) , i ) ) ) ) ;
1260+ }
1261+ }
1262+ }
1263+
1264+ var bestScore = - 1 ;
1265+ var bestMismatchIndex = - 1 ;
1266+ ParameterInfo bestMismatchParameter = null ;
1267+ string bestKwargName = null ;
1268+ PyObject bestKwargValue = null ;
1269+
1270+ foreach ( var method in candidates )
1271+ {
1272+ if ( method == null || OperatorMethod . IsOperatorMethod ( method ) )
1273+ {
1274+ continue ;
1275+ }
1276+
1277+ var pi = method . GetParameters ( ) ;
1278+ var paramsArrayIndex = pi . Length > 0 && Attribute . IsDefined ( pi [ pi . Length - 1 ] , typeof ( ParamArrayAttribute ) )
1279+ ? pi . Length - 1
1280+ : - 1 ;
1281+
1282+ var score = 0 ;
1283+ var mismatchIndex = - 1 ;
1284+ var limit = Math . Min ( pyArgCount , pi . Length ) ;
1285+ for ( var i = 0 ; i < limit ; i ++ )
1286+ {
1287+ if ( i == paramsArrayIndex )
1288+ {
1289+ // Params-array element conversions aren't probed; count the tail as matched.
1290+ score = limit ;
1291+ break ;
1292+ }
1293+
1294+ var op = Runtime . PyTuple_GetItem ( args , i ) ;
1295+ if ( op == null )
1296+ {
1297+ Exceptions . Clear ( ) ;
1298+ break ;
1299+ }
1300+
1301+ if ( ! ArgumentMatchesParameter ( op , pi [ i ] ) )
1302+ {
1303+ mismatchIndex = i ;
1304+ break ;
1305+ }
1306+ score ++ ;
1307+ }
1308+
1309+ string kwargName = null ;
1310+ PyObject kwargValue = null ;
1311+ ParameterInfo kwargParameter = null ;
1312+ if ( mismatchIndex == - 1 && kwargs != null )
1313+ {
1314+ foreach ( var pair in kwargs )
1315+ {
1316+ var parameter = pi . FirstOrDefault ( p => p . Name == pair . Key || p . Name . ToSnakeCase ( ) == pair . Key ) ;
1317+ if ( parameter == null )
1318+ {
1319+ // Unknown keyword names are not this diagnosis' job.
1320+ continue ;
1321+ }
1322+
1323+ if ( ArgumentMatchesParameter ( pair . Value . Reference , parameter ) )
1324+ {
1325+ score ++ ;
1326+ }
1327+ else
1328+ {
1329+ kwargName = pair . Key ;
1330+ kwargValue = pair . Value ;
1331+ kwargParameter = parameter ;
1332+ break ;
1333+ }
1334+ }
1335+ }
1336+
1337+ if ( mismatchIndex == - 1 && kwargName == null )
1338+ {
1339+ // Everything given matched: nothing to pinpoint for this candidate.
1340+ continue ;
1341+ }
1342+
1343+ if ( score > bestScore )
1344+ {
1345+ bestScore = score ;
1346+ bestMismatchIndex = mismatchIndex ;
1347+ bestKwargName = kwargName ;
1348+ bestKwargValue = kwargValue ;
1349+ bestMismatchParameter = mismatchIndex != - 1 ? pi [ mismatchIndex ] : kwargParameter ;
1350+ }
1351+ }
1352+
1353+ if ( bestMismatchParameter == null )
1354+ {
1355+ return string . Empty ;
1356+ }
1357+
1358+ var expected = MethodSignatureFormatter . FormatType ( bestMismatchParameter . ParameterType ) ;
1359+ var parameterName = bestMismatchParameter . Name . ToSnakeCase ( ) ;
1360+ if ( bestKwargName != null )
1361+ {
1362+ return $ "Argument mismatch: keyword argument '{ bestKwargName } ' expected { expected } , got { Runtime . PyObject_GetTypeName ( bestKwargValue . Reference ) } .";
1363+ }
1364+
1365+ var mismatchedArg = Runtime . PyTuple_GetItem ( args , bestMismatchIndex ) ;
1366+ var got = mismatchedArg == null ? Util . BadStr : Runtime . PyObject_GetTypeName ( mismatchedArg ) ;
1367+ return $ "Argument mismatch: argument { bestMismatchIndex + 1 } ('{ parameterName } ') expected { expected } , got { got } .";
1368+ }
1369+ catch
1370+ {
1371+ // Best-effort hint only; never mask the original failure.
1372+ return string . Empty ;
1373+ }
1374+ finally
1375+ {
1376+ // Conversion probes may have left a Python error set.
1377+ Exceptions . Clear ( ) ;
1378+ }
1379+ }
1380+
1381+ /// <summary>
1382+ /// Mirror of the binder's per-argument acceptance rules, used to find the first
1383+ /// mismatching argument. Lenient where probing is unreliable (by-ref, generic
1384+ /// and untyped parameters) so it under-reports rather than blames the wrong
1385+ /// argument.
1386+ /// </summary>
1387+ private static bool ArgumentMatchesParameter ( BorrowedReference op , ParameterInfo parameter )
1388+ {
1389+ var parameterType = parameter . ParameterType ;
1390+ if ( parameterType . IsByRef || parameterType . ContainsGenericParameters || parameterType == typeof ( object ) )
1391+ {
1392+ return true ;
1393+ }
1394+
1395+ Type clrtype = null ;
1396+ using ( var pyoptype = Runtime . PyObject_Type ( op ) )
1397+ {
1398+ Exceptions . Clear ( ) ;
1399+ if ( ! pyoptype . IsNull ( ) )
1400+ {
1401+ clrtype = Converter . GetTypeByAlias ( pyoptype . Borrow ( ) ) ;
1402+ }
1403+ }
1404+
1405+ if ( clrtype == null )
1406+ {
1407+ // Not a primitive-aliased value (e.g. a wrapped CLR object): probe the conversion.
1408+ var converted = Converter . ToManaged ( op , parameterType , out _ , false ) ;
1409+ Exceptions . Clear ( ) ;
1410+ return converted ;
1411+ }
1412+
1413+ if ( parameterType == clrtype )
1414+ {
1415+ return true ;
1416+ }
1417+
1418+ var pytype = Converter . GetPythonTypeByAlias ( parameterType ) ;
1419+ using ( var pyoptype = Runtime . PyObject_Type ( op ) )
1420+ {
1421+ Exceptions . Clear ( ) ;
1422+ if ( ! pyoptype . IsNull ( ) && pytype == pyoptype . Borrow ( ) )
1423+ {
1424+ return true ;
1425+ }
1426+ }
1427+
1428+ var underlyingType = Nullable . GetUnderlyingType ( parameterType ) ?? parameterType ;
1429+ if ( Type . GetTypeCode ( underlyingType ) == Type . GetTypeCode ( clrtype ) )
1430+ {
1431+ return true ;
1432+ }
1433+
1434+ if ( underlyingType == typeof ( decimal ) || underlyingType == typeof ( double )
1435+ || ( Runtime . PyFloat_Check ( op ) && Type . GetTypeCode ( underlyingType ) . IsInteger ( ) && ! underlyingType . IsEnum ) )
1436+ {
1437+ var converted = Converter . ToManaged ( op , parameterType , out _ , false ) ;
1438+ Exceptions . Clear ( ) ;
1439+ if ( converted )
1440+ {
1441+ return true ;
1442+ }
1443+ }
1444+
1445+ var opImplicit = parameterType . GetMethod ( "op_Implicit" , new [ ] { clrtype } ) ;
1446+ return opImplicit != null && opImplicit . ReturnType == parameterType ;
1447+ }
1448+
12191449 protected static void AppendArgumentTypes ( StringBuilder to , BorrowedReference args )
12201450 {
12211451 long argCount = Runtime . PyTuple_Size ( args ) ;
0 commit comments