Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions docs/source/common_issues.rst
Original file line number Diff line number Diff line change
Expand Up @@ -863,3 +863,60 @@ False:

If you use the :option:`--warn-unreachable <mypy --warn-unreachable>` flag, mypy will generate
an error about each unreachable code block.

Unpacking Dictionary in keyword arguments
-----------------------------------------

If you are unpacking a dictionary into a Callable's arguments, then
you need to ensure the types of dictionary members by providing either
a TypedDict or a variable with type Dict[str, Any].

For example, the following function calls are valid:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a suggestion - maybe an example that shows some offending cases or invalid function calls would help drive the point home? (otherwise good job!)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, will add them.


.. code-block:: python

def func(a: int, b: str, c: List[int]):
...

some_dict: Dict[str, Any] = {
"a": 1,
"b": "somestr",
"c": [1, 2, 3],
}

func(**some_dict)

TypedArgs = TypedDict("TypedArgs", {"a": int, "b": str, "c": List[float]})

some_other_dict: TypedArgs = {
"a": 1,
"b": "somestr",
"c": [1, 2, 3],
}

func(**some_other_dict)

The following function calls are invalid, on account of untyped/mistyped unpacking:

.. code-block:: python

def func(a: int, b: str, c: List[int]):
...

some_dict = {
"a": 1,
"b": "somestr",
"c": [1, 2, 3],
}

func(**some_dict)

TypedArgs = TypedDict("TypedArgs", {"a": int, "b": str, "c": List[str]})

some_other_dict: TypedArgs = {
"a": 1,
"b": "somestr",
"c": ["1", "2", "3"],
}

func(**some_other_dict)
1 change: 1 addition & 0 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,7 @@ def incompatible_argument(self,
arg_type_str = '*' + arg_type_str
elif arg_kind == ARG_STAR2:
arg_type_str = '**' + arg_type_str
notes.append("Use TypedDict or Dict[str, Any] for unpacked **kwargs")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey! You will also have to modify the tests affected by this change :) Cheers!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea, its a work in progress


# For function calls with keyword arguments, display the argument name rather than the
# number.
Expand Down
3 changes: 2 additions & 1 deletion test-data/unit/check-columns.test
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ def g(**x: int) -> None: pass
a = ['']
f(*a) # E:4: Argument 1 to "f" has incompatible type "*List[str]"; expected "int"
b = {'x': 'y'}
g(**b) # E:5: Argument 1 to "g" has incompatible type "**Dict[str, str]"; expected "int"
g(**b) # E:5: Argument 1 to "g" has incompatible type "**Dict[str, str]"; expected "int" \
# N:5: Use TypedDict or Dict[str, Any] for unpacked **kwargs
[builtins fixtures/dict.pyi]

[case testColumnsMultipleStatementsPerLine]
Expand Down
3 changes: 2 additions & 1 deletion test-data/unit/check-expressions.test
Original file line number Diff line number Diff line change
Expand Up @@ -2217,7 +2217,8 @@ kw2 = {'x': ''}
d2 = dict(it, **kw2)
d2() # E: "Dict[str, object]" not callable

d3 = dict(it, **kw2) # type: Dict[str, int] # E: Argument 2 to "dict" has incompatible type "**Dict[str, str]"; expected "int"
d3 = dict(it, **kw2) # type: Dict[str, int] # E: Argument 2 to "dict" has incompatible type "**Dict[str, str]"; expected "int" \
# N: Use TypedDict or Dict[str, Any] for unpacked **kwargs
[builtins fixtures/dict.pyi]

[case testDictFromIterableAndStarStarArgs2]
Expand Down
16 changes: 11 additions & 5 deletions test-data/unit/check-kwargs.test
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,11 @@ d = None # type: Dict[str, A]
f(**d)
f(x=A(), **d)
d2 = None # type: Dict[str, B]
f(**d2) # E: Argument 1 to "f" has incompatible type "**Dict[str, B]"; expected "A"
f(x=A(), **d2) # E: Argument 2 to "f" has incompatible type "**Dict[str, B]"; expected "A"
f(**d2) # E: Argument 1 to "f" has incompatible type "**Dict[str, B]"; expected "A" \
# N: Use TypedDict or Dict[str, Any] for unpacked **kwargs
f(x=A(), **d2) # E: Argument 2 to "f" has incompatible type "**Dict[str, B]"; expected "A" \
# N: Use TypedDict or Dict[str, Any] for unpacked **kwargs

class A: pass
class B: pass
[builtins fixtures/dict.pyi]
Expand Down Expand Up @@ -363,7 +366,8 @@ def f(a: 'A', b: 'B') -> None: pass
d = None # type: Dict[str, Any]
f(**d)
d2 = None # type: Dict[str, A]
f(**d2) # E: Argument 1 to "f" has incompatible type "**Dict[str, A]"; expected "B"
f(**d2) # E: Argument 1 to "f" has incompatible type "**Dict[str, A]"; expected "B" \
# N: Use TypedDict or Dict[str, Any] for unpacked **kwargs
class A: pass
class B: pass
[builtins fixtures/dict.pyi]
Expand Down Expand Up @@ -417,7 +421,8 @@ f(**a) # okay

b = {'': ''}
f(b) # E: Argument 1 to "f" has incompatible type "Dict[str, str]"; expected "int"
f(**b) # E: Argument 1 to "f" has incompatible type "**Dict[str, str]"; expected "int"
f(**b) # E: Argument 1 to "f" has incompatible type "**Dict[str, str]"; expected "int" \
# N: Use TypedDict or Dict[str, Any] for unpacked **kwargs

c = {0: 0}
f(**c) # E: Keywords must be strings
Expand Down Expand Up @@ -462,7 +467,8 @@ def g(arg: int = 0, **kwargs: object) -> None:

d = {} # type: Dict[str, object]
f(**d)
g(**d) # E: Argument 1 to "g" has incompatible type "**Dict[str, object]"; expected "int"
g(**d) # E: Argument 1 to "g" has incompatible type "**Dict[str, object]"; expected "int" \
# N: Use TypedDict or Dict[str, Any] for unpacked **kwargs

m = {} # type: Mapping[str, object]
f(**m)
Expand Down
30 changes: 20 additions & 10 deletions test-data/unit/check-typeddict.test
Original file line number Diff line number Diff line change
Expand Up @@ -1504,8 +1504,10 @@ def f6(x: int, z: str) -> None: pass

a: A
f1(**a)
f2(**a) # E: Argument "y" to "f2" has incompatible type "str"; expected "int"
f3(**a) # E: Argument "x" to "f3" has incompatible type "int"; expected "B"
f2(**a) # E: Argument "y" to "f2" has incompatible type "str"; expected "int" \
# N: Use TypedDict or Dict[str, Any] for unpacked **kwargs
f3(**a) # E: Argument "x" to "f3" has incompatible type "int"; expected "B" \
# N: Use TypedDict or Dict[str, Any] for unpacked **kwargs
f4(**a) # E: Extra argument "y" from **args for "f4"
f5(**a) # E: Too few arguments for "f5"
f6(**a) # E: Extra argument "y" from **args for "f6"
Expand Down Expand Up @@ -1536,13 +1538,16 @@ def g(x: int, **kwargs: str) -> None: ...

a: A
b: B
f(**a) # E: Argument 1 to "f" has incompatible type "**A"; expected "str"
f(**a) # E: Argument 1 to "f" has incompatible type "**A"; expected "str" \
# N: Use TypedDict or Dict[str, Any] for unpacked **kwargs
f(**b)
g(**a)
g(**b) # E: Argument "x" to "g" has incompatible type "str"; expected "int"
g(**b) # E: Argument "x" to "g" has incompatible type "str"; expected "int" \
# N: Use TypedDict or Dict[str, Any] for unpacked **kwargs
g(1, **a) # E: "g" gets multiple values for keyword argument "x"
g(1, **b) # E: "g" gets multiple values for keyword argument "x" \
# E: Argument "x" to "g" has incompatible type "str"; expected "int"
# E: Argument "x" to "g" has incompatible type "str"; expected "int" \
# N: Use TypedDict or Dict[str, Any] for unpacked **kwargs
[builtins fixtures/dict.pyi]

[case testTypedDictAsStarStarTwice]
Expand All @@ -1561,13 +1566,18 @@ b: B
c: C
f1(**a, **b)
f1(**b, **a)
f2(**a, **b) # E: Argument "y" to "f2" has incompatible type "str"; expected "float"
f3(**a, **b) # E: Argument "z" to "f3" has incompatible type "bytes"; expected "float"
f3(**b, **a) # E: Argument "z" to "f3" has incompatible type "bytes"; expected "float"
f2(**a, **b) # E: Argument "y" to "f2" has incompatible type "str"; expected "float" \
# N: Use TypedDict or Dict[str, Any] for unpacked **kwargs
f3(**a, **b) # E: Argument "z" to "f3" has incompatible type "bytes"; expected "float" \
# N: Use TypedDict or Dict[str, Any] for unpacked **kwargs
f3(**b, **a) # E: Argument "z" to "f3" has incompatible type "bytes"; expected "float" \
# N: Use TypedDict or Dict[str, Any] for unpacked **kwargs
f1(**a, **c) # E: "f1" gets multiple values for keyword argument "x" \
# E: Argument "x" to "f1" has incompatible type "str"; expected "int"
# E: Argument "x" to "f1" has incompatible type "str"; expected "int" \
# N: Use TypedDict or Dict[str, Any] for unpacked **kwargs
f1(**c, **a) # E: "f1" gets multiple values for keyword argument "x" \
# E: Argument "x" to "f1" has incompatible type "str"; expected "int"
# E: Argument "x" to "f1" has incompatible type "str"; expected "int" \
# N: Use TypedDict or Dict[str, Any] for unpacked **kwargs
[builtins fixtures/tuple.pyi]

[case testTypedDictNonMappingMethods]
Expand Down