Skip to content

Annotate types without Py_TPFLAGS_BASETYPE as @final - #1043

Merged
provinzkraut merged 4 commits into
msgspec:mainfrom
sobolevn:final-types
Jun 10, 2026
Merged

provinzkraut merged 4 commits into
msgspec:mainfrom
sobolevn:final-types

Conversation

@sobolevn

@sobolevn sobolevn commented Jun 7, 2026 •

Copy link
Copy Markdown
Member

Annotate types that can't be used as base classes:

>>> import msgspec

>>> class A(msgspec.Raw): ...
Traceback (most recent call last):
  File "<python-input-1>", line 1, in <module>
    class A(msgspec.Raw): ...
TypeError: type 'msgspec.Raw' is not an acceptable base type

>>> class A(msgspec.Meta): ...
Traceback (most recent call last):
  File "<python-input-2>", line 1, in <module>
    class A(msgspec.Meta): ...
TypeError: type 'msgspec.Meta' is not an acceptable base type

@provinzkraut
provinzkraut added this pull request to the merge queue Jun 10, 2026
Merged via the queue into msgspec:main with commit 29cf293 Jun 10, 2026
23 checks passed
pull Bot pushed a commit to Mu-L/msgspec that referenced this pull request Sep 5, 2026
Closes msgspec#1056.

Adds a `stubtest` step to the `test-typing` job so stub/runtime drift is
caught in CI instead of by hand (as in msgspec#1043, msgspec#1062). `mypy` is already
in the `test-typing` group, so no new dependency.

### Root-cause fixes (instead of allowlisting)

- **Positional-only `__text_signature__`.** `json`/`msgpack`
`encode`/`decode` and the `Encoder`/`Decoder` methods take their first
arg positional-only at runtime, but their C `__text_signature__` omitted
the `/`, so `inspect.signature` reported them as positional-or-keyword
and stubtest flagged 10 mismatches. Added the `/` in `_core.c` so
introspection matches runtime; behaviour is unchanged (keyword calls
already raised). This removes all 10 pos-only allowlist entries.
- **`StructMeta` `@disjoint_base`.** `StructMeta` is a disjoint base at
runtime; marked it `@disjoint_base` in the stub (the stub already
imports from `typing_extensions`, so no new floor of consequence).
Removes that allowlist entry.

### Other stub fixes

- `structs.pyi` was missing `__all__`.
- `json.format`s stub marked `buf` positional-only (`/`), but the
runtime accepts it as a keyword, so the `/` is dropped to match (this is
the one callable that really takes a keyword).

### Allowlist

What is left in `tests/typing/stubtest_allowlist.txt` is 10 structural
false positives stubtest cannot see through: the per-class `__init__`
synthesized in C for every `Struct`/`inspect` type, the custom `__new__`
on `Meta`/`Ext`/`UnsetType`, `Struct.__init_subclass__` (config kwargs
via `dataclass_transform`, surfaces on 3.14+), and the private `_utils`
module.

Run with `--ignore-unused-allowlist`. The `test-typing` job runs
stubtest on a single Python resolved from `python-version-file:
pyproject.toml` (currently 3.14, floats up over time), not a pinned
matrix, so a single superset allowlist with `--ignore-unused` is robust
to the runner Python moving. Verified green on 3.12, 3.14, 3.15.

---------

Co-authored-by: Siyet <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants