← Back to index

callables_kwargs.py

True Positive
False Positive
False Negative
Optional (detected)
Warning or Info
TP: 4
FP: 4
FN: 9
Optional: 0 / 2
1"""
2Tests the use of an unpacked TypedDict for annotating **kwargs.
3"""
4
5# Specification: https://typing.readthedocs.io/en/latest/spec/callables.html#unpack-for-keyword-arguments
6
7# This sample tests the handling of Unpack[TypedDict] when used with
8# a **kwargs parameter in a function signature.
9
10from typing import Protocol, TypeVar, TypedDict, NotRequired, Required, Unpack, assert_type
13class TD1(TypedDict):
14 v1: Required[int]
15 v2: NotRequired[str]
18class TD2(TD1):
19 v3: Required[str]
22def func1(**kwargs: Unpack[TD2]) -> None:
23 v1 = kwargs["v1"]
24 assert_type(v1, int)
Unexpected error [type-assertion-failure] Type `int` does not match asserted type `@Todo`
26 # > Type checkers may allow reading an item using ``d['x']`` even if
27 # > the key ``'x'`` is not required
28 kwargs["v2"] # E?: v2 may not be present
30 if "v2" in kwargs:
31 v2 = kwargs["v2"]
32 assert_type(v2, str)
Unexpected error [type-assertion-failure] Type `str` does not match asserted type `@Todo`
34 v3 = kwargs["v3"]
35 assert_type(v3, str)
Unexpected error [type-assertion-failure] Type `str` does not match asserted type `@Todo`
38def func2(v3: str, **kwargs: Unpack[TD1]) -> None:
39 # > When Unpack is used, type checkers treat kwargs inside the function
40 # > body as a TypedDict.
41 assert_type(kwargs, TD1)
Unexpected error [type-assertion-failure] Type `TD1` does not match asserted type `dict[str, @Todo]`
44def func3() -> None:
45 # Mypy reports multiple errors here.
46 func1() # E: missing required keyword args
Expected a ty diagnostic for this line
47 func1(v1=1, v2="", v3="5") # OK
49 td2 = TD2(v1=2, v3="4")
50 func1(**td2) # OK
51 func1(v1=1, v2="", v3="5", v4=5) # E: v4 is not in TD2
Expected a ty diagnostic for this line
52 func1(1, "", "5") # E: args not passed by position
[too-many-positional-arguments] Too many positional arguments to function `func1`: expected 0, got 3
54 # > Passing a dictionary of type dict[str, object] as a **kwargs argument
55 # > to a function that has **kwargs annotated with Unpack must generate a
56 # > type checker error.
57 my_dict: dict[str, str] = {}
58 func1(**my_dict) # E: untyped dict
Expected a ty diagnostic for this line
60 d1 = {"v1": 2, "v3": "4", "v4": 4}
61 func1(**d1) # E?: OK or Type error (spec allows either)
62 func2(**td2) # OK
63 func1(v1=2, **td2) # E: v1 is already specified
Expected a ty diagnostic for this line
64 func2(1, **td2) # E: v1 is already specified
[invalid-argument-type] Argument to function `func2` is incorrect: Expected `str`, found `Literal[1]` [parameter-already-assigned] Multiple values provided for parameter `v3` of function `func2`
65 func2(v1=1, **td2) # E: v1 is already specified
Expected a ty diagnostic for this line
68class TDProtocol1(Protocol):
69 def __call__(self, *, v1: int, v3: str) -> None:
70 ...
73class TDProtocol2(Protocol):
74 def __call__(self, *, v1: int, v3: str, v2: str = "") -> None:
75 ...
78class TDProtocol3(Protocol):
79 def __call__(self, *, v1: int, v2: int, v3: str) -> None:
80 ...
83class TDProtocol4(Protocol):
84 def __call__(self, *, v1: int) -> None:
85 ...
88class TDProtocol5(Protocol):
89 def __call__(self, v1: int, v3: str) -> None:
90 ...
93class TDProtocol6(Protocol):
94 def __call__(self, **kwargs: Unpack[TD2]) -> None:
95 ...
97# Specification: https://typing.readthedocs.io/en/latest/spec/callables.html#assignment
99v1: TDProtocol1 = func1 # OK
100v2: TDProtocol2 = func1 # OK
101v3: TDProtocol3 = func1 # E: v2 is wrong type
Expected a ty diagnostic for this line
102v4: TDProtocol4 = func1 # E: v3 is missing
Expected a ty diagnostic for this line
103v5: TDProtocol5 = func1 # E: params are positional
[invalid-assignment] Object of type `def func1(**kwargs: @Todo) -> None` is not assignable to `TDProtocol5`
104v6: TDProtocol6 = func1 # OK
107def func4(v1: int, /, **kwargs: Unpack[TD2]) -> None:
108 ...
111def func5(v1: int, **kwargs: Unpack[TD2]) -> None: # E: parameter v1 overlaps with the TypedDict.
Expected a ty diagnostic for this line
112 ...
115T = TypeVar("T", bound=TD2)
117# > TypedDict is the only permitted heterogeneous type for typing **kwargs.
118# > Therefore, in the context of typing **kwargs, using Unpack with types other
119# > than TypedDict should not be allowed and type checkers should generate
120# > errors in such cases.
122def func6(**kwargs: Unpack[T]) -> None: # E: unpacked value must be a TypedDict, not a TypeVar bound to TypedDict.
Expected a ty diagnostic for this line
123 ...
125# > The situation where the destination callable contains **kwargs: Unpack[TypedDict] and
126# > the source callable doesn’t contain **kwargs should be disallowed. This is because,
127# > we cannot be sure that additional keyword arguments are not being passed in when an instance of a subclass
128# > had been assigned to a variable with a base class type and then unpacked in the destination callable invocation
130def func7(*, v1: int, v3: str, v2: str = "") -> None:
131 ...
134v7: TDProtocol6 = func7 # E: source does not have kwargs
[invalid-assignment] Object of type `def func7(*, v1: int, v3: str, v2: str = "") -> None` is not assignable to `TDProtocol6`