From 4310586460e0af07fa8994a0b4f03cb323e352f0 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Wed, 27 Mar 2024 01:37:09 +0100 Subject: [PATCH] Fix TypedDict init from Type with optional keys (#17068) Followup to #16963 Correctly set optional and required keys for the TypedDict init callable. Ref: https://github.com/python/mypy/issues/11644 --- mypy/checkexpr.py | 5 ++- test-data/unit/check-typeddict.test | 32 ++++++++++++++++--- test-data/unit/lib-stub/typing_extensions.pyi | 2 ++ 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index e7567eafb8fe..24d8447cdf3e 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -949,7 +949,10 @@ def typeddict_callable(self, info: TypeInfo) -> CallableType: def typeddict_callable_from_context(self, callee: TypedDictType) -> CallableType: return CallableType( list(callee.items.values()), - [ArgKind.ARG_NAMED] * len(callee.items), + [ + ArgKind.ARG_NAMED if name in callee.required_keys else ArgKind.ARG_NAMED_OPT + for name in callee.items + ], list(callee.items.keys()), callee, self.named_type("builtins.type"), diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 639be7bde8d8..bd1fbe3f2667 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -3450,16 +3450,40 @@ reveal_type(p) # N: Revealed type is "TypedDict('__main__.Params', {'x': builtin [case testInitTypedDictFromType] from typing import TypedDict, Type +from typing_extensions import Required -class Point(TypedDict): - x: int +class Point(TypedDict, total=False): + x: Required[int] y: int def func(cls: Type[Point]) -> None: - reveal_type(cls) # N: Revealed type is "Type[TypedDict('__main__.Point', {'x': builtins.int, 'y': builtins.int})]" + reveal_type(cls) # N: Revealed type is "Type[TypedDict('__main__.Point', {'x': builtins.int, 'y'?: builtins.int})]" cls(x=1, y=2) cls(1, 2) # E: Too many positional arguments - cls(x=1) # E: Missing named argument "y" + cls(x=1) + cls(y=2) # E: Missing named argument "x" cls(x=1, y=2, error="") # E: Unexpected keyword argument "error" [typing fixtures/typing-full.pyi] [builtins fixtures/tuple.pyi] + +[case testInitTypedDictFromTypeGeneric] +from typing import Generic, TypedDict, Type, TypeVar +from typing_extensions import Required + +class Point(TypedDict, total=False): + x: Required[int] + y: int + +T = TypeVar("T", bound=Point) + +class A(Generic[T]): + def __init__(self, a: Type[T]) -> None: + self.a = a + + def func(self) -> T: + reveal_type(self.a) # N: Revealed type is "Type[T`1]" + self.a(x=1, y=2) + self.a(y=2) # E: Missing named argument "x" + return self.a(x=1) +[typing fixtures/typing-full.pyi] +[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/lib-stub/typing_extensions.pyi b/test-data/unit/lib-stub/typing_extensions.pyi index b7b738f63d92..b5bfc1ab3f20 100644 --- a/test-data/unit/lib-stub/typing_extensions.pyi +++ b/test-data/unit/lib-stub/typing_extensions.pyi @@ -39,6 +39,8 @@ Never: _SpecialForm TypeVarTuple: _SpecialForm Unpack: _SpecialForm +Required: _SpecialForm +NotRequired: _SpecialForm @final class TypeAliasType: