msgspec.convert(obj, float) converts a python int to a C double via
PyLong_AsDouble without checking for overflow. For an int too large to
represent as a float, PyLong_AsDouble sets OverflowError but still
returns -1.0, and that value was passed straight through to
ms_decode_float and returned to the interpreter with the exception
still set - which surfaces as SystemError: <built-in function convert>
returned a result with an exception set, bypassing the documented
ValidationError contract entirely.
json.decode already handles the equivalent case correctly, raising
ValidationError: Number out of range. Apply the same
value-error-with-path check convert() already uses elsewhere in this
file (e.g. _constr_as_f64) so the overflow is reported the same way,
for both bare and nested (e.g. dict[str, float]) targets, under both
strict and lax mode.
Fixes msgspec#1122.
Summary
msgspec.convert(obj, float) converts a Python int to a C double via PyLong_AsDouble without checking for overflow. For an int too large to represent as a finite float (e.g. 10**400), PyLong_AsDouble sets OverflowError internally but still returns -1.0, and that value was passed straight through to ms_decode_float and returned to the interpreter with the exception still set — which surfaces as:
This bypasses the documented ValidationError contract entirely, so a caller wrapping the call in except msgspec.ValidationError doesn't catch it.
json.decode already handles the equivalent case correctly, raising ValidationError: Number out of range. This PR applies the same "check PyErr_Occurred() after a lossy C conversion, then report via ms_error_with_path" pattern already used elsewhere in this file (e.g. _constr_as_f64), so convert() reports the overflow the same way — for both bare (convert(big, float)) and nested (convert({"x": big}, dict[str, float])) targets, under both strict=True and strict=False.
Fixes #1122.
Testing