From a5dcac8e0db7959e9c51a9656911d0580d32190d Mon Sep 17 00:00:00 2001 From: momohatt Date: Thu, 22 Oct 2020 23:12:46 +0900 Subject: [PATCH 1/5] Infer type of **kwargs with a context of Mapping[str, Any] --- mypy/checkexpr.py | 6 +++++- test-data/unit/check-kwargs.test | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index c186ab1434ef..a7671dc222f7 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1103,7 +1103,11 @@ def infer_arg_types_in_context( # Fill in the rest of the argument types. for i, t in enumerate(res): if not t: - res[i] = self.accept(args[i]) + if arg_kinds[i] == ARG_STAR2: + res[i] = self.accept(args[i], self.chk.named_generic_type('typing.Mapping', + [self.named_type('builtins.str'), AnyType(TypeOfAny.special_form)])) + else: + res[i] = self.accept(args[i]) assert all(tp is not None for tp in res) return cast(List[Type], res) diff --git a/test-data/unit/check-kwargs.test b/test-data/unit/check-kwargs.test index 96669e7eea36..5cd288bfc8e9 100644 --- a/test-data/unit/check-kwargs.test +++ b/test-data/unit/check-kwargs.test @@ -491,3 +491,9 @@ m = {} # type: Mapping[str, object] f(**m) g(**m) # TODO: Should be an error [builtins fixtures/dict.pyi] + +[case testPassingEmptyDictAsKwarg] +def f(x=0): pass + +f(**{}) +[builtins fixtures/dict.pyi] From 2a88bb87f86014ce22236b20980bbbc36d2eb362 Mon Sep 17 00:00:00 2001 From: momohatt Date: Fri, 23 Oct 2020 15:52:18 +0900 Subject: [PATCH 2/5] Relax 'Too many arguments' check for **kwargs --- mypy/checkexpr.py | 8 +++++--- test-data/unit/check-ctypes.test | 2 +- test-data/unit/check-kwargs.test | 4 ++-- test-data/unit/check-typeddict.test | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index a7671dc222f7..6b73b063caa1 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1405,11 +1405,13 @@ def check_for_extra_actual_arguments(self, ok = True # False if we've found any error for i, kind in enumerate(actual_kinds): - if i not in all_actuals and ( - kind != nodes.ARG_STAR or + if (i not in all_actuals and # We accept the other iterables than tuple (including Any) # as star arguments because they could be empty, resulting no arguments. - is_non_empty_tuple(actual_types[i])): + (kind != nodes.ARG_STAR or is_non_empty_tuple(actual_types[i])) and + # Accept all types for double-starred arguments, because they could be empty + # dictionaries and we can't tell it from their types + kind != nodes.ARG_STAR2): # Extra actual: not matched by a formal argument. ok = False if kind != nodes.ARG_NAMED: diff --git a/test-data/unit/check-ctypes.test b/test-data/unit/check-ctypes.test index f6e55a451794..5771f4c24be6 100644 --- a/test-data/unit/check-ctypes.test +++ b/test-data/unit/check-ctypes.test @@ -182,6 +182,6 @@ import ctypes intarr4 = ctypes.c_int * 4 x = {"a": 1, "b": 2} -intarr4(**x) # E: Too many arguments for "Array" +intarr4(**x) [builtins fixtures/floatdict.pyi] diff --git a/test-data/unit/check-kwargs.test b/test-data/unit/check-kwargs.test index 5cd288bfc8e9..be2b55130e0e 100644 --- a/test-data/unit/check-kwargs.test +++ b/test-data/unit/check-kwargs.test @@ -396,7 +396,7 @@ class A: pass from typing import Any, Dict def f(*args: 'A') -> None: pass d = None # type: Dict[Any, Any] -f(**d) # E: Too many arguments for "f" +f(**d) class A: pass [builtins fixtures/dict.pyi] @@ -493,7 +493,7 @@ g(**m) # TODO: Should be an error [builtins fixtures/dict.pyi] [case testPassingEmptyDictAsKwarg] -def f(x=0): pass +def f(): pass f(**{}) [builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 2c474f389ad4..c58ccd521d37 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -1584,8 +1584,8 @@ d = None # type: Dict[Any, Any] f1(**td, **d) f1(**d, **td) -f2(**td, **d) # E: Too many arguments for "f2" -f2(**d, **td) # E: Too many arguments for "f2" +f2(**td, **d) +f2(**d, **td) [builtins fixtures/dict.pyi] [case testTypedDictNonMappingMethods] From 13dc4d21b296c71da6b03a32d23dd5d88cde6781 Mon Sep 17 00:00:00 2001 From: momohatt Date: Fri, 23 Oct 2020 16:05:33 +0900 Subject: [PATCH 3/5] Rename test --- test-data/unit/check-kwargs.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/check-kwargs.test b/test-data/unit/check-kwargs.test index be2b55130e0e..27c801b11ce0 100644 --- a/test-data/unit/check-kwargs.test +++ b/test-data/unit/check-kwargs.test @@ -492,7 +492,7 @@ f(**m) g(**m) # TODO: Should be an error [builtins fixtures/dict.pyi] -[case testPassingEmptyDictAsKwarg] +[case testPassingEmptyDictWithStars] def f(): pass f(**{}) From ff90cf3b2e044f2358f4adeb0264d1710c61e7df Mon Sep 17 00:00:00 2001 From: momohatt Date: Sat, 31 Oct 2020 15:51:01 +0900 Subject: [PATCH 4/5] Relax check on kwarg key type --- mypy/checkexpr.py | 30 ++++++++++-------------------- test-data/unit/check-kwargs.test | 2 ++ 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 6b73b063caa1..86496ba22a1a 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1103,11 +1103,7 @@ def infer_arg_types_in_context( # Fill in the rest of the argument types. for i, t in enumerate(res): if not t: - if arg_kinds[i] == ARG_STAR2: - res[i] = self.accept(args[i], self.chk.named_generic_type('typing.Mapping', - [self.named_type('builtins.str'), AnyType(TypeOfAny.special_form)])) - else: - res[i] = self.accept(args[i]) + res[i] = self.accept(args[i]) assert all(tp is not None for tp in res) return cast(List[Type], res) @@ -3940,21 +3936,15 @@ def is_valid_var_arg(self, typ: Type) -> bool: def is_valid_keyword_var_arg(self, typ: Type) -> bool: """Is a type valid as a **kwargs argument?""" - if self.chk.options.python_version[0] >= 3: - return is_subtype(typ, self.chk.named_generic_type( - 'typing.Mapping', [self.named_type('builtins.str'), - AnyType(TypeOfAny.special_form)])) - else: - return ( - is_subtype(typ, self.chk.named_generic_type( - 'typing.Mapping', - [self.named_type('builtins.str'), - AnyType(TypeOfAny.special_form)])) - or - is_subtype(typ, self.chk.named_generic_type( - 'typing.Mapping', - [self.named_type('builtins.unicode'), - AnyType(TypeOfAny.special_form)]))) + ret = ( + is_subtype(typ, self.chk.named_generic_type('typing.Mapping', + [self.named_type('builtins.str'), AnyType(TypeOfAny.special_form)])) or + is_subtype(typ, self.chk.named_generic_type('typing.Mapping', + [UninhabitedType(), UninhabitedType()]))) + if self.chk.options.python_version[0] < 3: + ret = ret or is_subtype(typ, self.chk.named_generic_type('typing.Mapping', + [self.named_type('builtins.unicode'), AnyType(TypeOfAny.special_form)])) + return ret def has_member(self, typ: Type, member: str) -> bool: """Does type have member with the given name?""" diff --git a/test-data/unit/check-kwargs.test b/test-data/unit/check-kwargs.test index 27c801b11ce0..a33d6f5e0ce4 100644 --- a/test-data/unit/check-kwargs.test +++ b/test-data/unit/check-kwargs.test @@ -494,6 +494,8 @@ g(**m) # TODO: Should be an error [case testPassingEmptyDictWithStars] def f(): pass +def g(x=1): pass f(**{}) +g(**{}) [builtins fixtures/dict.pyi] From 989512b8cd1873321e80475df1aa0ef472ae3e81 Mon Sep 17 00:00:00 2001 From: momohatt Date: Sat, 7 Nov 2020 00:39:38 +0900 Subject: [PATCH 5/5] Add a test to prevent inferring Dict[str, Any] --- test-data/unit/check-kwargs.test | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-kwargs.test b/test-data/unit/check-kwargs.test index a33d6f5e0ce4..e842e8ed5721 100644 --- a/test-data/unit/check-kwargs.test +++ b/test-data/unit/check-kwargs.test @@ -299,8 +299,9 @@ 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" +f(x=A(), **d2) # E: Argument 2 to "f" has incompatible type "**Dict[str, B]"; expected "A" +f(**{'x': B()}) # E: Argument 1 to "f" has incompatible type "**Dict[str, B]"; expected "A" class A: pass class B: pass [builtins fixtures/dict.pyi]