Skip to content

Commit f4db3cd

Browse files
authored
stubtest: basic support for unpack kwargs (#21024)
Fixes #21023
1 parent 1b7ff0e commit f4db3cd

2 files changed

Lines changed: 53 additions & 1 deletion

File tree

mypy/stubtest.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -968,7 +968,21 @@ def from_funcitem(stub: nodes.FuncItem) -> Signature[nodes.Argument]:
968968
elif stub_arg.kind == nodes.ARG_STAR:
969969
stub_sig.varpos = stub_arg
970970
elif stub_arg.kind == nodes.ARG_STAR2:
971-
stub_sig.varkw = stub_arg
971+
if stub_arg.variable.type is not None and isinstance(
972+
(typed_dict_arg := mypy.types.get_proper_type(stub_arg.variable.type)),
973+
mypy.types.TypedDictType,
974+
):
975+
for key_name, key_type in typed_dict_arg.items.items():
976+
optional = key_name not in typed_dict_arg.required_keys
977+
stub_sig.kwonly[key_name] = nodes.Argument(
978+
nodes.Var(key_name, key_type),
979+
type_annotation=key_type,
980+
initializer=nodes.EllipsisExpr() if optional else None,
981+
kind=nodes.ARG_NAMED_OPT if optional else nodes.ARG_NAMED,
982+
pos_only=False,
983+
)
984+
else:
985+
stub_sig.varkw = stub_arg
972986
else:
973987
raise AssertionError
974988
return stub_sig

mypy/test/teststubtest.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ def __getitem__(self, typeargs: Any) -> object: ...
6161
Literal = 0
6262
NewType = 0
6363
TypedDict = 0
64+
Unpack = 0
6465
6566
class TypeVar:
6667
def __init__(self, name, covariant: bool = ..., contravariant: bool = ...) -> None: ...
@@ -766,6 +767,43 @@ def test_varargs_varkwargs(self) -> Iterator[Case]:
766767
error="k6",
767768
)
768769

770+
@collect_cases
771+
def test_kwargs_unpack_typeddict(self) -> Iterator[Case]:
772+
yield Case(
773+
stub="""
774+
from typing import TypedDict, Unpack, type_check_only
775+
776+
@type_check_only
777+
class _Args(TypedDict):
778+
a: int
779+
b: int
780+
781+
def f1(**kwargs: Unpack[_Args]) -> None: ...
782+
""",
783+
runtime="def f1(*, a, b): pass",
784+
error=None,
785+
)
786+
yield Case(
787+
stub="def f2(**kwargs: Unpack[_Args]) -> None: ...",
788+
runtime="def f2(*, a, c): pass",
789+
error="f2",
790+
)
791+
yield Case(
792+
stub="""
793+
@type_check_only
794+
class _OptionalArgs(TypedDict, total=False):
795+
a: int
796+
797+
def f3(**kwargs: Unpack[_OptionalArgs]) -> None: ...
798+
def f4(**kwargs: Unpack[_OptionalArgs]) -> None: ...
799+
""",
800+
runtime="""
801+
def f3(*, a): pass
802+
def f4(*, a=0): pass
803+
""",
804+
error="f3",
805+
)
806+
769807
@collect_cases
770808
def test_overload(self) -> Iterator[Case]:
771809
yield Case(

0 commit comments

Comments
 (0)