From 07b1c7f8e2a4cc644dfdd3ac32075a5e5d4957c4 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 1 Oct 2025 13:10:49 -0400 Subject: [PATCH 1/5] feat: validate mypyc_attr args This PR implements validation for `mypyc_attr` arguments and more informational type hints for its signature --- mypy_extensions.py | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/mypy_extensions.py b/mypy_extensions.py index 1910000..c3f3d9c 100644 --- a/mypy_extensions.py +++ b/mypy_extensions.py @@ -5,12 +5,32 @@ from mypy_extensions import TypedDict """ -from typing import Any, Dict +from typing import Any, Dict, Final, Literal import sys # _type_check is NOT a part of public typing API, it is used here only to mimic # the (convenient) behavior of types provided by typing module. -from typing import _type_check # type: ignore +from typing import _type_check # type: ignore [attr-defined] + +from typing_extensions import NotRequired, TypeGuard, Unpack + + +MYPYC_ATTRS: Final = frozenset([ + "native_class", + "allow_interpreted_subclasses", + "serializable", +]) + +MypycAttr = Literal[ + "native_class", + "allow_interpreted_subclasses", + "serializable", +] + +class MypycAttrs(TypedDict): + native_class: NotRequired[bool] + allow_interpreted_subclasses: NotRequired[bool] + serializable: NotRequired[bool] def _check_fails(cls, other): @@ -158,7 +178,21 @@ def trait(cls): return cls -def mypyc_attr(*attrs, **kwattrs): +def _validate_mypyc_attr_key(key: str) -> TypeGuard[MypycAttr]: + if key not in MYPYC_ATTRS: + raise ValueError( + f"{key!r} is not a valid `mypyc_attr` key.\n" + "Valid keys are: {', '.join(map(repr, sorted(MYPYC_ATTRS)))}" + ) + + +def mypyc_attr(*attrs: MypycAttr, **kwattrs: Unpack[MypycAttrs]): + for key in attrs: + _validate_mypyc_attr_key(key) + for key, value in kwattrs.items(): + _validate_mypyc_attr_key(key) + if not isinstance(value, bool): + raise TypeError(f"{key} value should be boolean, not {type(value).__name__}.") return lambda x: x From e1723030de1f57b4cda3d5209ae5c279a1f5fd63 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 1 Oct 2025 13:16:00 -0400 Subject: [PATCH 2/5] Update mypy_extensions.py From 21f5e20ea22321a29130fab797f177af54fa73d7 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 1 Oct 2025 13:21:37 -0400 Subject: [PATCH 3/5] Update mypy_extensions.py --- mypy_extensions.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mypy_extensions.py b/mypy_extensions.py index c3f3d9c..b7fd590 100644 --- a/mypy_extensions.py +++ b/mypy_extensions.py @@ -5,7 +5,7 @@ from mypy_extensions import TypedDict """ -from typing import Any, Dict, Final, Literal +from typing import Any, Callable, Dict, Final, Literal, TypeVar import sys # _type_check is NOT a part of public typing API, it is used here only to mimic @@ -15,6 +15,8 @@ from typing_extensions import NotRequired, TypeGuard, Unpack +_C = TypeVar("_C", bound=Callable[..., Any]) + MYPYC_ATTRS: Final = frozenset([ "native_class", "allow_interpreted_subclasses", @@ -186,7 +188,7 @@ def _validate_mypyc_attr_key(key: str) -> TypeGuard[MypycAttr]: ) -def mypyc_attr(*attrs: MypycAttr, **kwattrs: Unpack[MypycAttrs]): +def mypyc_attr(*attrs: MypycAttr, **kwattrs: Unpack[MypycAttrs]) -> Callable[[_C], _C]: for key in attrs: _validate_mypyc_attr_key(key) for key, value in kwattrs.items(): @@ -196,8 +198,7 @@ def mypyc_attr(*attrs: MypycAttr, **kwattrs: Unpack[MypycAttrs]): return lambda x: x -# TODO: We may want to try to properly apply this to any type -# variables left over... +# TODO: We may want to try to properly apply this to any type variables left over... class _FlexibleAliasClsApplied: def __init__(self, val): self.val = val From 2660b9f968b8ae104ab6e16b21bb5cfed3bac991 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 1 Oct 2025 14:16:32 -0400 Subject: [PATCH 4/5] add free_list_len to MypycAttrs --- mypy_extensions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mypy_extensions.py b/mypy_extensions.py index b7fd590..cfa5c1f 100644 --- a/mypy_extensions.py +++ b/mypy_extensions.py @@ -33,6 +33,7 @@ class MypycAttrs(TypedDict): native_class: NotRequired[bool] allow_interpreted_subclasses: NotRequired[bool] serializable: NotRequired[bool] + free_list_len: NotRequired[int] def _check_fails(cls, other): From 36f8fde977e32dc087a18b551594034da7565d38 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:04:24 -0400 Subject: [PATCH 5/5] Update mypy_extensions.py --- mypy_extensions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mypy_extensions.py b/mypy_extensions.py index cfa5c1f..90a2d18 100644 --- a/mypy_extensions.py +++ b/mypy_extensions.py @@ -21,12 +21,14 @@ "native_class", "allow_interpreted_subclasses", "serializable", + "free_list_len", ]) MypycAttr = Literal[ "native_class", "allow_interpreted_subclasses", "serializable", + "free_list_len", ] class MypycAttrs(TypedDict):