You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 2f0b50d
Browse filesBrowse the repository at this point in the historyBrowse files
Correct the reported signatures of several public callables (#1197)
`inspect.signature` and `help()` are built from a text signature: the
first line of a C-level callable's docstring, before the `--` separator.
That line has to parse as a Python parameter list, and it has to match
how the callable is really called. Fourteen of those lines in `_core.c`
fail one or the other, and between them they cover sixteen callables.
Three do not parse. `msgspec.Raw` declares `Raw(msg="", /)`, which is
two adjacent C string literals concatenating to `Raw(msg=, /)`.
`msgspec.StructMeta` declares `*, **struct_config`.
`msgspec.structs.replace` declares `replace(struct, / **changes)`,
missing a comma. For `Raw`, `inspect.signature` raises `ValueError:
<class 'msgspec.Raw'> builtin has invalid signature` on every supported
version, and the API docs render `class msgspec.Raw` with no parameters.
`replace` behaves that way from Python 3.12 onwards: up to 3.11 the
stray `/` was stripped along with the rest of the non-Python syntax,
leaving something that parsed, and from 3.12 it is kept. `StructMeta`
has a `__signature__` getset, so its string only reached `help()` and
`docs/api.rst`, which hard-codes the same text.
Four more parse, but describe the wrong calling convention. Keyword
arguments are rejected at runtime by `msgspec.msgpack.Ext`,
`msgspec.structs.asdict`, `msgspec.structs.astuple` and
`msgspec.structs.force_setattr`, yet their parameters are marked as
positional-or-keyword. The stubs carry the same omission, so
`msgspec.structs.asdict(struct=p)` passes type checking and raises
`TypeError` when run.
The remaining seven begin with a bare `self` rather than CPython's
`$self` marker, so the instance becomes a parameter of the bound method:
`inspect.signature(encoder.encode)` gives `(self, obj, /)`, and binding
a single argument to that fails although `encoder.encode(obj)` is the
correct call. Nine bound methods are affected: `encode`, `encode_into`
and `encode_lines` on `json.Encoder`, `encode` and `encode_into` on
`msgpack.Encoder`, `decode` and `decode_lines` on `json.Decoder`,
`decode` on `msgpack.Decoder`, and `Raw.copy`. In `Raw.copy` that `self`
carries no trailing `/` either, so it was reported as
keyword-addressable on top of being reported at all.
Ten stub declarations gain the matching `/`.
The unit tests assert the exact reported signature of fifteen callables
against a literal, and compile the `StructMeta` text signature. Five
negative checks in `tests/typing/basic_typing_examples.py` pin the
tightened stubs in that file's existing idiom. `stubtest` couples four
of the ten stub edits to the C side by itself; the `Ext.__init__` edit
is pinned by nothing else, and the five `self` edits have no observable
effect to pin. On current main, fifteen of the sixteen unit cases fail
on Python 3.11 and all sixteen on 3.14, and mypy, pyright and pyrefly
each flag all five negative checks as unused ignores.
Not addressed here: four decoder strings give `type='Any'` as a default,
where the real default is `typing.Any` rather than that string. A text
signature cannot carry that value. A default written as a name is
evaluated, and the result is kept only if it is one of a handful of
scalar types, so `type=Any` and `type=typing.Any` alike make the whole
signature invalid and `inspect.signature` then raises for the callable.
Correcting this one means going outside the text signature.
This continues #1116, which corrected nine such strings.
---------
Co-authored-by: Tseluiko Aleksandr <[email protected]>
0 commit comments