Skip to content

Commit d86a0b6

Browse files
jhonabreulclaude
andcommitted
Render overload hint in snake_case (method and parameter names)
The "No method matches" hint now uses the snake_case names Python callers actually use, both in the message header and in each candidate signature: No method matches given arguments for compute_scaled: (<class 'float'>). The expected signature is: compute_scaled(Int32 scale_factor) - Add SnakeCaseName(MethodBase) and use it for the header and FormatSignature (constructors keep their special .ctor token). - Snake_case parameter names in FormatSignature via Name.ToSnakeCase(). - Add tests: method name and parameter names are snake_cased for single and multiple overloads. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent da7cc7f commit d86a0b6

2 files changed

Lines changed: 62 additions & 6 deletions

File tree

src/embed_tests/TestFloatToIntConversion.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ def overloaded_ctor(value):
3232
3333
def overloaded_method(value):
3434
return OverloadedIntTaker(0).Echo(value)
35+
36+
def single_named(value):
37+
return IntTaker(0).ComputeValue(value)
38+
39+
def overloaded_named(value):
40+
return OverloadedIntTaker(0).ComputeRange(value)
41+
42+
def single_params(value):
43+
return IntTaker(0).ComputeScaled(value)
3544
";
3645

3746
[OneTimeSetUp]
@@ -95,6 +104,33 @@ public void ErrorMessage_MultipleOverloads_ListsCandidates()
95104
// The int overload is surfaced, hinting an integer was expected.
96105
StringAssert.Contains("Int32 range", ex.Message);
97106
}
107+
108+
// The hinted signatures use the snake_case name Python callers use, not the
109+
// original C# name.
110+
[Test]
111+
public void ErrorMessage_SingleOverload_UsesSnakeCaseMethodName()
112+
{
113+
var ex = Assert.Throws<PythonException>(() => Call("single_named", 5.5));
114+
StringAssert.Contains("compute_value(", ex.Message);
115+
StringAssert.DoesNotContain("ComputeValue", ex.Message);
116+
}
117+
118+
[Test]
119+
public void ErrorMessage_MultipleOverloads_UseSnakeCaseMethodName()
120+
{
121+
var ex = Assert.Throws<PythonException>(() => Call("overloaded_named", 5.5));
122+
StringAssert.Contains("compute_range(", ex.Message);
123+
StringAssert.DoesNotContain("ComputeRange", ex.Message);
124+
}
125+
126+
// The hinted signatures also snake_case the parameter names.
127+
[Test]
128+
public void ErrorMessage_SignatureParameters_AreSnakeCase()
129+
{
130+
var ex = Assert.Throws<PythonException>(() => Call("single_params", 5.5));
131+
StringAssert.Contains("scale_factor", ex.Message);
132+
StringAssert.DoesNotContain("scaleFactor", ex.Message);
133+
}
98134
}
99135

100136
public class IntTaker
@@ -107,6 +143,10 @@ public IntTaker(int value)
107143
}
108144

109145
public int Echo(int value) => value;
146+
147+
public int ComputeValue(int value) => value;
148+
149+
public int ComputeScaled(int scaleFactor) => scaleFactor;
110150
}
111151

112152
/// <summary>
@@ -131,5 +171,9 @@ public OverloadedIntTaker(int range, PyObject selector, PyObject volumeSelector
131171
public int Echo(int value, System.Func<int, int> selector = null) => value;
132172

133173
public int Echo(int value, PyObject selector, PyObject other = null) => value;
174+
175+
public int ComputeRange(int value, System.Func<int, int> selector = null) => value;
176+
177+
public int ComputeRange(int value, PyObject selector, PyObject other = null) => value;
134178
}
135179
}

src/runtime/MethodBinder.cs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,13 +1006,14 @@ internal virtual NewReference Invoke(BorrowedReference inst, BorrowedReference a
10061006
if (!Exceptions.ErrorOccurred())
10071007
{
10081008
var value = new StringBuilder("No method matches given arguments");
1009+
// Use the snake_case name Python callers use, matching the hinted signatures below.
10091010
if (methodinfo != null && methodinfo.Length > 0)
10101011
{
1011-
value.Append($" for {methodinfo[0].Name}");
1012+
value.Append($" for {SnakeCaseName(methodinfo[0])}");
10121013
}
10131014
else if (list.Count > 0)
10141015
{
1015-
value.Append($" for {list[0].MethodBase.Name}");
1016+
value.Append($" for {SnakeCaseName(list[0].MethodBase)}");
10161017
}
10171018

10181019
value.Append(": ");
@@ -1288,13 +1289,15 @@ private static void AppendOverloads(StringBuilder to, IEnumerable<MethodBase> me
12881289
}
12891290

12901291
/// <summary>
1291-
/// Formats a method/constructor as a readable signature, e.g.
1292-
/// <c>RangeConsolidator(Int32 range, Func[IBaseData, Decimal] selector = None)</c>.
1292+
/// Formats a method/constructor as a readable signature using the snake_case
1293+
/// name Python callers use, e.g.
1294+
/// <c>range_consolidator(Int32 range, Func[IBaseData, Decimal] selector = None)</c>.
1295+
/// The constructor's special <c>.ctor</c> token is left as-is.
12931296
/// </summary>
12941297
private static string FormatSignature(MethodBase method)
12951298
{
12961299
var to = new StringBuilder();
1297-
to.Append(method.Name).Append('(');
1300+
to.Append(SnakeCaseName(method)).Append('(');
12981301
var parameters = method.GetParameters();
12991302
for (var i = 0; i < parameters.Length; i++)
13001303
{
@@ -1307,7 +1310,7 @@ private static string FormatSignature(MethodBase method)
13071310
{
13081311
to.Append("params ");
13091312
}
1310-
to.Append(FormatType(parameter.ParameterType)).Append(' ').Append(parameter.Name);
1313+
to.Append(FormatType(parameter.ParameterType)).Append(' ').Append(parameter.Name.ToSnakeCase());
13111314
if (parameter.IsOptional)
13121315
{
13131316
to.Append(" = ").Append(FormatDefaultValue(parameter.DefaultValue));
@@ -1349,6 +1352,15 @@ private static string FormatType(Type type)
13491352
return type.Name;
13501353
}
13511354

1355+
/// <summary>
1356+
/// The snake_case name a Python caller uses for the given method. Constructors
1357+
/// keep their special <c>.ctor</c> token (a Python caller invokes the type).
1358+
/// </summary>
1359+
private static string SnakeCaseName(MethodBase method)
1360+
{
1361+
return method.IsConstructor ? method.Name : method.Name.ToSnakeCase();
1362+
}
1363+
13521364
private static string FormatDefaultValue(object value)
13531365
{
13541366
if (value == null || value is DBNull)

0 commit comments

Comments
 (0)