From 10194b4434b66d50fc4b514a748bbca15b3a0ca0 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Thu, 30 Mar 2023 19:32:08 +0100 Subject: [PATCH 1/2] Add a failing test case --- mypy/test/teststubtest.py | 22 ++++++++++++++----- test-data/unit/lib-stub/typing_extensions.pyi | 4 ++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index f57cbe98bb8f8..c30864c6cc281 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -1572,24 +1572,36 @@ class _Options(TypedDict): ) @collect_cases - def test_protocol(self) -> Iterator[Case]: + def test_runtime_typing_objects(self) -> Iterator[Case]: + yield Case( + stub="from typing_extensions import Protocol, TypedDict", + runtime="from typing_extensions import Protocol, TypedDict", + error=None, + ) yield Case( stub=""" - from typing_extensions import Protocol - class X(Protocol): bar: int def foo(self, x: int, y: bytes = ...) -> str: ... """, runtime=""" - from typing_extensions import Protocol - class X(Protocol): bar: int def foo(self, x: int, y: bytes = ...) -> str: ... """, error=None, ) + yield Case( + stub=""" + class Y(TypedDict): + a: int + """, + runtime=""" + class Y(TypedDict): + a: int + """, + error=None, + ) @collect_cases def test_type_var(self) -> Iterator[Case]: diff --git a/test-data/unit/lib-stub/typing_extensions.pyi b/test-data/unit/lib-stub/typing_extensions.pyi index 759f956d314b9..3202c3d49e01c 100644 --- a/test-data/unit/lib-stub/typing_extensions.pyi +++ b/test-data/unit/lib-stub/typing_extensions.pyi @@ -56,6 +56,10 @@ class _TypedDict(Mapping[str, object]): if sys.version_info < (3, 0): def has_key(self, k: str) -> bool: ... def __delitem__(self, k: NoReturn) -> None: ... + # Stubtest's tests need the following items: + __required_keys__: frozenset[str] + __optional_keys__: frozenset[str] + __total__: bool def TypedDict(typename: str, fields: Dict[str, Type[_T]], *, total: Any = ...) -> Type[dict]: ... From b1eec1b8f684aca0aa094bac1d4b2d25cac10047 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Thu, 30 Mar 2023 19:44:52 +0100 Subject: [PATCH 2/2] Fix the failing test --- mypy/stubtest.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index c7472af8f2bb0..a4b572c206c83 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -26,7 +26,7 @@ from functools import singledispatch from pathlib import Path from typing import Any, Generic, Iterator, TypeVar, Union -from typing_extensions import get_origin +from typing_extensions import get_origin, is_typeddict import mypy.build import mypy.modulefinder @@ -436,12 +436,12 @@ class SubClass(runtime): # type: ignore[misc] def _verify_metaclass( - stub: nodes.TypeInfo, runtime: type[Any], object_path: list[str] + stub: nodes.TypeInfo, runtime: type[Any], object_path: list[str], *, is_runtime_typeddict: bool ) -> Iterator[Error]: # We exclude protocols, because of how complex their implementation is in different versions of - # python. Enums are also hard, ignoring. + # python. Enums are also hard, as are runtime TypedDicts; ignoring. # TODO: check that metaclasses are identical? - if not stub.is_protocol and not stub.is_enum: + if not stub.is_protocol and not stub.is_enum and not is_runtime_typeddict: runtime_metaclass = type(runtime) if runtime_metaclass is not type and stub.metaclass_type is None: # This means that runtime has a custom metaclass, but a stub does not. @@ -485,7 +485,10 @@ def verify_typeinfo( return yield from _verify_final(stub, runtime, object_path) - yield from _verify_metaclass(stub, runtime, object_path) + is_runtime_typeddict = stub.typeddict_type is not None and is_typeddict(runtime) + yield from _verify_metaclass( + stub, runtime, object_path, is_runtime_typeddict=is_runtime_typeddict + ) # Check everything already defined on the stub class itself (i.e. not inherited) to_check = set(stub.names) @@ -493,7 +496,7 @@ def verify_typeinfo( to_check.update( m for m in vars(runtime) if not is_probably_private(m) and m not in IGNORABLE_CLASS_DUNDERS ) - # Special-case the __init__ method for Protocols + # Special-case the __init__ method for Protocols and the __new__ method for TypedDicts # # TODO: On Python <3.11, __init__ methods on Protocol classes # are silently discarded and replaced. @@ -501,6 +504,8 @@ def verify_typeinfo( # Ideally, we'd figure out a good way of validating Protocol __init__ methods on 3.11+. if stub.is_protocol: to_check.discard("__init__") + if is_runtime_typeddict: + to_check.discard("__new__") for entry in sorted(to_check): mangled_entry = entry