Skip to content

Commit 1943ff5

Browse files
committed
Extend the no-match error with the unexpected keyword argument instead of replacing it
1 parent 3d9173a commit 1943ff5

2 files changed

Lines changed: 28 additions & 36 deletions

File tree

src/runtime/MethodBinder.cs

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,12 +1016,6 @@ internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference a
10161016
// If we already have an exception pending, don't create a new one
10171017
if (!Exceptions.ErrorOccurred())
10181018
{
1019-
// Unknown kwarg names get the Python-style error; the generic message below does not echo kwargs.
1020-
if (TryRaiseUnexpectedKeywordArgumentError(kw, info, methodinfo))
1021-
{
1022-
return default;
1023-
}
1024-
10251019
var value = new StringBuilder("No method matches given arguments");
10261020
// Use the snake_case name Python callers use, matching the hinted signatures below.
10271021
if (methodinfo != null && methodinfo.Length > 0)
@@ -1036,6 +1030,10 @@ internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference a
10361030
value.Append(": ");
10371031
AppendArgumentTypes(to: value, args);
10381032

1033+
// The argument types echo above covers positional args only; name the first
1034+
// unknown kwarg (if any) so a misspelled keyword argument is visible.
1035+
AppendUnexpectedKeywordArgument(value, kw, info);
1036+
10391037
// List the candidate overloads so the caller can see what was
10401038
// expected (e.g. that an int overload exists when a float was
10411039
// passed). Applies to every "no match" case, not just numeric ones.
@@ -1045,7 +1043,12 @@ internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference a
10451043
var overloads = MethodSignatureFormatter.FormatOverloads(candidates);
10461044
if (overloads.Length > 0)
10471045
{
1048-
value.Append(". ").Append(overloads);
1046+
// The kwarg hint may already end the sentence with a question mark.
1047+
if (value[value.Length - 1] != '?')
1048+
{
1049+
value.Append('.');
1050+
}
1051+
value.Append(' ').Append(overloads);
10491052
}
10501053

10511054
Exceptions.RaiseTypeError(value.ToString());
@@ -1130,15 +1133,16 @@ internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference a
11301133
}
11311134

11321135
/// <summary>
1133-
/// Raises "got an unexpected keyword argument" and returns true when a kwarg name is
1134-
/// accepted by no candidate overload; returns false to let the generic no-match error be raised.
1136+
/// Appends "Got an unexpected keyword argument" to the no-match message when a kwarg
1137+
/// name is accepted by no candidate overload, with a "Did you mean" suggestion when a
1138+
/// similar parameter name exists. Appends nothing when every kwarg name is valid.
11351139
/// </summary>
1136-
private bool TryRaiseUnexpectedKeywordArgumentError(BorrowedReference kw, MethodBase info, MethodInfo[] methodinfo)
1140+
private void AppendUnexpectedKeywordArgument(StringBuilder to, BorrowedReference kw, MethodBase info)
11371141
{
11381142
var kwCount = kw == null ? 0 : (int)Runtime.PyDict_Size(kw);
11391143
if (kwCount <= 0)
11401144
{
1141-
return false;
1145+
return;
11421146
}
11431147

11441148
// Same candidate set Bind considered; ParameterNames are already in the caller's convention.
@@ -1170,32 +1174,15 @@ private bool TryRaiseUnexpectedKeywordArgumentError(BorrowedReference kw, Method
11701174

11711175
if (unexpectedName == null)
11721176
{
1173-
return false;
1174-
}
1175-
1176-
string methodName = null;
1177-
if (methodinfo != null && methodinfo.Length > 0)
1178-
{
1179-
methodName = MethodSignatureFormatter.SnakeCaseName(methodinfo[0]);
1180-
}
1181-
else if (list.Count > 0)
1182-
{
1183-
methodName = MethodSignatureFormatter.SnakeCaseName(list[0].MethodBase);
1184-
}
1185-
if (string.IsNullOrEmpty(methodName))
1186-
{
1187-
return false;
1177+
return;
11881178
}
11891179

1190-
var message = $"{methodName}() got an unexpected keyword argument '{unexpectedName}'";
1180+
to.Append($". Got an unexpected keyword argument '{unexpectedName}'");
11911181
var suggestion = ClosestParameterName(unexpectedName, parameterNames);
11921182
if (suggestion != null)
11931183
{
1194-
message += $". Did you mean '{suggestion}'?";
1184+
to.Append($". Did you mean '{suggestion}'?");
11951185
}
1196-
1197-
Exceptions.RaiseTypeError(message);
1198-
return true;
11991186
}
12001187

12011188
/// <summary>

tests/test_method.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,30 +1108,33 @@ def test_unexpected_keyword_argument_with_suggestion():
11081108
with pytest.raises(TypeError) as excinfo:
11091109
MethodTest.order_like_method("SPY", 10, as_tag="EmergencyFlatten")
11101110
message = str(excinfo.value)
1111-
assert "order_like_method() got an unexpected keyword argument 'as_tag'" in message
1111+
assert "No method matches given arguments for order_like_method" in message
1112+
assert "Got an unexpected keyword argument 'as_tag'" in message
11121113
assert "Did you mean 'tag'?" in message
11131114

11141115
# PascalCase call path: parameter names are the original ones.
11151116
with pytest.raises(TypeError) as excinfo:
11161117
MethodTest.OrderLikeMethod("SPY", 10, asTag="EmergencyFlatten")
11171118
message = str(excinfo.value)
1118-
assert "order_like_method() got an unexpected keyword argument 'asTag'" in message
1119+
assert "No method matches given arguments for order_like_method" in message
1120+
assert "Got an unexpected keyword argument 'asTag'" in message
11191121
assert "Did you mean 'tag'?" in message
11201122

11211123

11221124
def test_unexpected_keyword_argument_without_suggestion():
11231125
with pytest.raises(TypeError) as excinfo:
11241126
MethodTest.order_like_method("SPY", 10, completely_unrelated_name=1)
11251127
message = str(excinfo.value)
1126-
assert "order_like_method() got an unexpected keyword argument " \
1128+
assert "No method matches given arguments for order_like_method" in message
1129+
assert "Got an unexpected keyword argument " \
11271130
"'completely_unrelated_name'" in message
11281131
assert "Did you mean" not in message
11291132

11301133

11311134
def test_unexpected_keyword_argument_reports_first_in_call_order():
11321135
with pytest.raises(TypeError) as excinfo:
11331136
MethodTest.order_like_method("SPY", 10, first_bogus=1, second_bogus=2)
1134-
assert "got an unexpected keyword argument 'first_bogus'" in str(excinfo.value)
1137+
assert "Got an unexpected keyword argument 'first_bogus'" in str(excinfo.value)
11351138

11361139

11371140
def test_valid_keyword_arguments_still_bind():
@@ -1143,7 +1146,9 @@ def test_valid_keyword_argument_names_keep_no_match_message():
11431146
# 'd' is supplied both positionally and by name: valid names, unbindable call.
11441147
with pytest.raises(TypeError) as excinfo:
11451148
MethodTest.DefaultParams(1, 2, 3, 4, d=5)
1146-
assert "No method matches given arguments for default_params" in str(excinfo.value)
1149+
message = str(excinfo.value)
1150+
assert "No method matches given arguments for default_params" in message
1151+
assert "unexpected keyword argument" not in message
11471152

11481153
def test_optional_params():
11491154
res = MethodTest.OptionalParams(1, 2, 3, 4)

0 commit comments

Comments
 (0)