From 71db4a2bbf0d0dd6a4e11b8901cf2cafda5221b3 Mon Sep 17 00:00:00 2001 From: Kaushal Rohit Date: Sun, 7 Jun 2020 17:56:32 +0530 Subject: [PATCH 1/5] Added a note for unpacked kwargs --- mypy/messages.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mypy/messages.py b/mypy/messages.py index 941c7adc3634b..6399ba49d5913 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -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") # For function calls with keyword arguments, display the argument name rather than the # number. From 8cc842a506cdabdef09518f4ec7ea8f8d0b734ea Mon Sep 17 00:00:00 2001 From: Kaushal Rohit Date: Sun, 7 Jun 2020 17:56:46 +0530 Subject: [PATCH 2/5] Added unpacked kwargs --- docs/source/common_issues.rst | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/source/common_issues.rst b/docs/source/common_issues.rst index 704c9a23f76d1..f224669fa68e1 100644 --- a/docs/source/common_issues.rst +++ b/docs/source/common_issues.rst @@ -860,3 +860,35 @@ Here mypy will go on to check the last line as well and report ``Incompatible ty If we want to let mypy warn us of such unreachable code blocks, we can use the ``--warn-unreachable`` option. With this mypy will throw ``Statement is unreachable`` error along with the line number from where the unreachable block starts. + +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: + +.. 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("VarArgs", {"a": int, "b": str, "c": List[float]}) + + some_other_dict: TypedArgs = { + "a": 1, + "b": "somestr", + "c": [1, 2, 3], + } + + func(**some_other_dict) From 97f64f1b64d82debb12b84f22e4d7d86eb5784e1 Mon Sep 17 00:00:00 2001 From: Kaushal Rohit Date: Tue, 9 Jun 2020 20:15:40 +0530 Subject: [PATCH 3/5] Fixed test cases - added note in outputs --- test-data/unit/check-columns.test | 3 ++- test-data/unit/check-expressions.test | 3 ++- test-data/unit/check-kwargs.test | 16 +++++++++----- test-data/unit/check-typeddict.test | 30 ++++++++++++++++++--------- 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/test-data/unit/check-columns.test b/test-data/unit/check-columns.test index 339d0ce863a75..0d36fe076fad0 100644 --- a/test-data/unit/check-columns.test +++ b/test-data/unit/check-columns.test @@ -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] diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test index 9d423d10806d9..dd0563961df04 100644 --- a/test-data/unit/check-expressions.test +++ b/test-data/unit/check-expressions.test @@ -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] diff --git a/test-data/unit/check-kwargs.test b/test-data/unit/check-kwargs.test index 1dd450caae1b0..57b0648a17256 100644 --- a/test-data/unit/check-kwargs.test +++ b/test-data/unit/check-kwargs.test @@ -306,8 +306,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] @@ -370,7 +373,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] @@ -424,7 +428,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 @@ -469,7 +474,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) diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 4dc80b1ecd443..451f417852f34 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -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" @@ -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] @@ -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] From 9a4e59eea5d1f0415f328d4dd649241c4c6a2fa8 Mon Sep 17 00:00:00 2001 From: Kaushal Rohit Date: Tue, 9 Jun 2020 20:15:56 +0530 Subject: [PATCH 4/5] Added failing examples --- docs/source/common_issues.rst | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/docs/source/common_issues.rst b/docs/source/common_issues.rst index f224669fa68e1..299e82c7111ab 100644 --- a/docs/source/common_issues.rst +++ b/docs/source/common_issues.rst @@ -883,7 +883,7 @@ For example, the following function calls are valid: func(**some_dict) - TypedArgs = TypedDict("VarArgs", {"a": int, "b": str, "c": List[float]}) + TypedArgs = TypedDict("TypedArgs", {"a": int, "b": str, "c": List[float]}) some_other_dict: TypedArgs = { "a": 1, @@ -892,3 +892,28 @@ For example, the following function calls are valid: } 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) From f7770ee32a17cb5ed4c88de7f7ad1b5a4fa5faf7 Mon Sep 17 00:00:00 2001 From: Kaushal Rohit Date: Mon, 28 Sep 2020 08:20:25 +0530 Subject: [PATCH 5/5] BUG: fixed too short underline --- docs/source/common_issues.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/common_issues.rst b/docs/source/common_issues.rst index 0dc26546c4281..65be5e3545007 100644 --- a/docs/source/common_issues.rst +++ b/docs/source/common_issues.rst @@ -865,7 +865,7 @@ If you use the :option:`--warn-unreachable ` flag, mypy 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