From 25f186b875f73219ff1b37366ec116c8b027639b Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sun, 15 Mar 2026 13:40:37 -0700 Subject: [PATCH] stubtest: basic support for unpack kwargs Fixes #21023 --- mypy/stubtest.py | 19 ++++++++++++++++++- mypy/test/teststubtest.py | 22 ++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 216bc09914da..fcfc20603a22 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -941,7 +941,24 @@ def from_funcitem(stub: nodes.FuncItem) -> Signature[nodes.Argument]: elif stub_arg.kind == nodes.ARG_STAR: stub_sig.varpos = stub_arg elif stub_arg.kind == nodes.ARG_STAR2: - stub_sig.varkw = stub_arg + if stub_arg.variable.type is not None and isinstance( + (typed_dict_arg := mypy.types.get_proper_type(stub_arg.variable.type)), + mypy.types.TypedDictType, + ): + for key_name, key_type in typed_dict_arg.items.items(): + stub_sig.kwonly[key_name] = nodes.Argument( + nodes.Var(key_name, key_type), + type_annotation=key_type, + initializer=( + nodes.EllipsisExpr() + if key_name not in typed_dict_arg.required_keys + else None + ), + kind=nodes.ARG_NAMED, + pos_only=False, + ) + else: + stub_sig.varkw = stub_arg else: raise AssertionError return stub_sig diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index 87d04964f6b6..18458e1a1787 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -60,6 +60,7 @@ def __getitem__(self, typeargs: Any) -> object: ... Final = 0 Literal = 0 TypedDict = 0 +Unpack = 0 class TypeVar: def __init__(self, name, covariant: bool = ..., contravariant: bool = ...) -> None: ... @@ -765,6 +766,27 @@ def test_varargs_varkwargs(self) -> Iterator[Case]: error="k6", ) + @collect_cases + def test_kwargs_unpack_typeddict(self) -> Iterator[Case]: + yield Case( + stub=""" + from typing import TypedDict, Unpack + + class _Args(TypedDict): + a: int + b: int + + def f1(**kwargs: Unpack[_Args]) -> None: ... + """, + runtime="def f1(*, a, b): pass", + error=None, + ) + yield Case( + stub="def f2(**kwargs: Unpack[_Args]) -> None: ...", + runtime="def f2(*, a, c): pass", + error="f2", + ) + @collect_cases def test_overload(self) -> Iterator[Case]: yield Case(