diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 63b584e8652f4..5788211749946 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -707,6 +707,16 @@ def verify_typeinfo( mangled_entry = f"_{stub.name.lstrip('_')}{entry}" stub_to_verify = next((t.names[entry].node for t in stub.mro if entry in t.names), MISSING) assert stub_to_verify is not None + # A generic class is subscriptable at the type level via its type + # parameters; a runtime `__class_getitem__` is just the implementation + # detail backing that. Don't report it as missing from a stub that + # already declares the class as generic. See #21253. + if ( + entry == "__class_getitem__" + and isinstance(stub_to_verify, Missing) + and stub.is_generic() + ): + continue try: try: runtime_attr = getattr(runtime, mangled_entry) diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index 71555c03b9ad4..bdbb6ef9fb6db 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -292,6 +292,33 @@ def f(self, number, text): pass error=None, ) + @collect_cases + def test_class_getitem_on_generic(self) -> Iterator[Case]: + # A runtime `__class_getitem__` backs subscriptability that a stub + # expresses via generic type parameters, so it should not be reported + # as missing from a generic stub. See #21253. + yield Case( + stub=""" + from typing import Generic, TypeVar + _T = TypeVar("_T") + class GenericClassGetItem(Generic[_T]): ... + """, + runtime=""" + class GenericClassGetItem: + def __class_getitem__(cls, item, /): return cls + """, + error=None, + ) + # But a non-generic stub should still flag the extra runtime method. + yield Case( + stub="class PlainClassGetItem: ...", + runtime=""" + class PlainClassGetItem: + def __class_getitem__(cls, item, /): return cls + """, + error="PlainClassGetItem.__class_getitem__", + ) + @collect_cases def test_types(self) -> Iterator[Case]: yield Case(