Skip to content

Commit ec3a92f

Browse files
committed
stubtest: don't flag __class_getitem__ as missing on a generic stub
A class that implements `__class_getitem__` at runtime to support subscription is correctly modelled in a stub by declaring the class as generic (PEP 695 `class C[T]` or `Generic[T]`). stubtest nevertheless reported `C.__class_getitem__ is not present in stub` in that case. Skip the `__class_getitem__` runtime member when the stub class is generic (`TypeInfo.is_generic()`) and does not explicitly declare it. Non-generic stubs still report the missing method as before. Fixes #21253
1 parent 4c8f994 commit ec3a92f

2 files changed

Lines changed: 37 additions & 0 deletions

File tree

mypy/stubtest.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,16 @@ def verify_typeinfo(
707707
mangled_entry = f"_{stub.name.lstrip('_')}{entry}"
708708
stub_to_verify = next((t.names[entry].node for t in stub.mro if entry in t.names), MISSING)
709709
assert stub_to_verify is not None
710+
# A generic class is subscriptable at the type level via its type
711+
# parameters; a runtime `__class_getitem__` is just the implementation
712+
# detail backing that. Don't report it as missing from a stub that
713+
# already declares the class as generic. See #21253.
714+
if (
715+
entry == "__class_getitem__"
716+
and isinstance(stub_to_verify, Missing)
717+
and stub.is_generic()
718+
):
719+
continue
710720
try:
711721
try:
712722
runtime_attr = getattr(runtime, mangled_entry)

mypy/test/teststubtest.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,33 @@ def f(self, number, text): pass
292292
error=None,
293293
)
294294

295+
@collect_cases
296+
def test_class_getitem_on_generic(self) -> Iterator[Case]:
297+
# A runtime `__class_getitem__` backs subscriptability that a stub
298+
# expresses via generic type parameters, so it should not be reported
299+
# as missing from a generic stub. See #21253.
300+
yield Case(
301+
stub="""
302+
from typing import Generic, TypeVar
303+
_T = TypeVar("_T")
304+
class GenericClassGetItem(Generic[_T]): ...
305+
""",
306+
runtime="""
307+
class GenericClassGetItem:
308+
def __class_getitem__(cls, item, /): return cls
309+
""",
310+
error=None,
311+
)
312+
# But a non-generic stub should still flag the extra runtime method.
313+
yield Case(
314+
stub="class PlainClassGetItem: ...",
315+
runtime="""
316+
class PlainClassGetItem:
317+
def __class_getitem__(cls, item, /): return cls
318+
""",
319+
error="PlainClassGetItem.__class_getitem__",
320+
)
321+
295322
@collect_cases
296323
def test_types(self) -> Iterator[Case]:
297324
yield Case(

0 commit comments

Comments
 (0)