Accept integral floats for int params, hint overloads on bind failure (2.0.57)#128
Merged
jhonabreul merged 5 commits intoJul 6, 2026
Conversation
…gral Passing a Python float where a .NET integer parameter was expected behaved inconsistently: - Single-overload targets silently truncated any float (e.g. 5.5 -> 5) via Converter.ToManaged. - Overloaded targets rejected every float, including integral-valued ones (e.g. 5.0), with "No method matches given arguments" because the overload disambiguation path did not treat float->int as a valid conversion. This broke calls like RangeConsolidator(period) in Lean when period was a float. Make both paths consistent: an integral-valued float (5.0) is accepted and converted for integer parameters, while a non-integral float (5.5) is rejected with a TypeError instead of being silently truncated. - MethodBinder: treat integral Python floats as implicit-conversion candidates for integer parameters (enums excluded). - Converter.ToPrimitive: reject non-integral Python floats targeting integer types so truncation never happens silently. - Add a shared Type.IsInteger() helper in Util and use it in both places. - Add TestFloatToIntConversion covering single and overloaded ctor/method. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When argument binding fails, the raised TypeError only reported the argument
types that were passed, giving no clue what the method actually expected. For
example RangeConsolidator(5.5) produced:
No method matches given arguments for .ctor: (<class 'float'>)
Append the candidate overload signatures to the message so the caller can see
what was expected (e.g. that an int overload exists when a float was passed).
This applies to every "no match" case, not just numeric conversions.
- Single candidate -> ". The expected signature is:" + one signature.
- Multiple candidates -> ". The following overloads are available:" + list
(distinct, capped at 10 with "... and N more").
Signatures are rendered readably: friendly type names (by-ref/nullable
unwrapped, generics as Name[Arg1, Arg2]), params marked, optional parameters
shown with their default. The whole hint is best-effort and wrapped in a
try/catch so it can never mask the original binding failure.
- MethodBinder: add AppendOverloads/FormatSignature/FormatType/FormatDefaultValue
and emit the hint from Invoke's no-binding path.
- Add message tests to TestFloatToIntConversion (single and multiple overloads).
- Update TestCallbacks.TestNoOverloadException: the argument types are no longer
at the end of the message, so assert containment instead of suffix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
Bump AssemblyVersion/AssemblyFileVersion and the perf-test baseline reference to 2.0.57 to match the package <Version>. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jhonabreul
force-pushed
the
bug-integral-float-to-int-conversion
branch
from
July 2, 2026 22:00
189ef5f to
e3640ac
Compare
Martin-Molinero
approved these changes
Jul 6, 2026
Add a TypeCode-based IsInteger overload and reuse the TypeCode already computed by the callers in Converter.ToPrimitive and MethodBinder, instead of fetching it again inside IsInteger(Type). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Merged
5 tasks
jhonabreul
added a commit
that referenced
this pull request
Jul 10, 2026
…r Python-typed signatures (#136) * Extract overload hint formatting into public MethodSignatureFormatter Move the overload-signature hint logic added in #128 (AppendOverloads, FormatSignature, FormatType, SnakeCaseName, FormatDefaultValue) out of MethodBinder into a new public MethodSignatureFormatter class so it can be reused outside the binder. Downstream consumers (e.g. Lean) can now append the same "The following overloads are available:" hint to their own user-facing error messages when they reject a PyObject argument themselves. - FormatOverloads(methods, maxShown = 10, displayName = null) returns the hint text ("The expected signature is:" / "The following overloads are available:" plus one signature per line), or an empty string. Still best-effort: it never throws. - FormatSignature(method, displayName = null) is public; the optional displayName lets constructors render as the type name instead of the special .ctor token. - MethodBinder behavior is unchanged: the "No method matches given arguments" message is byte-identical. * Render overload hints with Python types The hinted signatures showed C# type names (Int32, TimeSpan, Func[...]), which a Python caller has to mentally translate. Render them with the Python types the runtime actually accepts for each parameter instead: - str/int/float/bool for strings, chars and numeric primitives - datetime / timedelta for DateTime / TimeSpan - Optional[T] for Nullable<T> - Callable[[args], ret] for delegates (None return for actions) - List[T] / Dict[K, V] for arrays, list and dictionary shapes - Any for object and PyObject (List[Any]/Dict[Any, Any] for PyList/PyDict) - CLR-only types (enums, classes) keep their Python-visible name Enum default values are rendered the way Python callers access them (e.g. StringComparison.ORDINAL) and bool defaults as True/False. Example: "range_consolidator(Int32 range, Func[IBaseData, Decimal] selector = None)" now renders as "range_consolidator(int range, Callable[[IBaseData], float] selector = None)". * Render overload hint parameters as Python annotations Python signatures annotate the name, not prefix the type: an argument rendered as "int arg_name" is actually "arg_name: int". Render the hinted signatures accordingly, e.g.: range_consolidator(range: int, selector: Callable[[IBaseData], float] = None) params arrays are rendered in Python variadic form, annotated with the element type: *values: int. * Skip PyObject overloads from the hinted signatures PyObject parameters accept any Python object and render as Any, so hinting them carries no type information: they are typically the very overloads that just rejected the value. Skip them in FormatOverloads so consumers do not have to filter them out themselves. If every candidate takes a PyObject, they are shown anyway rather than producing no hint at all.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two related improvements to method/constructor argument binding, plus a version bump.
1. Accept integral-valued floats for integer parameters; reject non-integral ones.
Passing a Python
floatwhere a .NET integer parameter was expected behaved inconsistently:5.5→5) viaConverter.ToManaged.5.0), with "No method matches given arguments", because the overload-disambiguation path did not treatfloat→intas a valid conversion. This broke calls likeRangeConsolidator(period)in Lean whenperiodwas a float.Both paths are now consistent: an integral-valued float (
5.0) is accepted and converted for integer parameters, while a non-integral float (5.5) is rejected with aTypeErrorinstead of being silently truncated.MethodBinder: treat integral Python floats as implicit-conversion candidates for integer parameters (enums excluded).Converter.ToPrimitive: reject non-integral Python floats targeting integer types so truncation never happens silently.Type.IsInteger()helper inUtiland used it in both places.2. Hint the candidate overloads in the "No method matches"
TypeError.When binding failed, the error only reported the argument types that were passed, giving no clue what the method actually expected. The message now appends the candidate overload signature(s), using the snake_case names Python callers use (method names and parameter names; constructors keep the special
.ctortoken). This applies to every "no match" case, not just numeric conversions.Single candidate — a method taking a single
int, showing the snake_case method and parameter name:Multiple candidates — an overloaded method, each candidate listed in snake_case:
The
Int32parameter is now visible, hinting that an integer was expected. For Lean'sRangeConsolidator(5.5)the two constructor overloads are likewise listed with snake_case parameters (e.g.volume_selector). Signatures are rendered readably (friendly type names, by-ref/nullable unwrapped, generics asName[Arg1, Arg2],paramsmarked, optional parameters shown with their default). The list is de-duplicated and capped at 10 with a "... and N more" suffix. The whole hint is best-effort and wrapped in atry/catchso it can never mask the original binding failure.3. Version bump.
QuantConnect.pythonnet<Version>bumped2.0.56→2.0.57(with the matchingAssemblyVersion/AssemblyFileVersioninProperties/AssemblyInfo.cs).