Skip to content

Commit ea7fed1

Browse files
authored
Fix argument checking on empty dict with double stars (#9629)
### Description Closes #5580 Previously, the type of empty dicts are inferred as `dict[<nothing>, <nothing>]`, which is not a subtype of `Mapping[str, Any]`, and this has caused a false-positive error saying `Keywords must be strings`. This PR fixes it by inferring the types of double-starred arguments with a context of `Mapping[str, Any]`. Closes #4001 and closes #9007 (duplicate) Do not check for "too many arguments" error when there are any double-starred arguments. This will lead to some false-negavites, see my comment here: #4001 (comment) ### Test Plan Added a simple test `testPassingEmptyDictWithStars`. This single test can cover both of the issues above. I also modified some existing tests that were added in #9573 and #6213.
1 parent 8c5c915 commit ea7fed1

File tree

4 files changed

+29
-24
lines changed

4 files changed

+29
-24
lines changed

mypy/checkexpr.py

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1415,11 +1415,13 @@ def check_for_extra_actual_arguments(self,
14151415
ok = True # False if we've found any error
14161416

14171417
for i, kind in enumerate(actual_kinds):
1418-
if i not in all_actuals and (
1419-
kind != nodes.ARG_STAR or
1418+
if (i not in all_actuals and
14201419
# We accept the other iterables than tuple (including Any)
14211420
# as star arguments because they could be empty, resulting no arguments.
1422-
is_non_empty_tuple(actual_types[i])):
1421+
(kind != nodes.ARG_STAR or is_non_empty_tuple(actual_types[i])) and
1422+
# Accept all types for double-starred arguments, because they could be empty
1423+
# dictionaries and we can't tell it from their types
1424+
kind != nodes.ARG_STAR2):
14231425
# Extra actual: not matched by a formal argument.
14241426
ok = False
14251427
if kind != nodes.ARG_NAMED:
@@ -3944,21 +3946,15 @@ def is_valid_var_arg(self, typ: Type) -> bool:
39443946

39453947
def is_valid_keyword_var_arg(self, typ: Type) -> bool:
39463948
"""Is a type valid as a **kwargs argument?"""
3947-
if self.chk.options.python_version[0] >= 3:
3948-
return is_subtype(typ, self.chk.named_generic_type(
3949-
'typing.Mapping', [self.named_type('builtins.str'),
3950-
AnyType(TypeOfAny.special_form)]))
3951-
else:
3952-
return (
3953-
is_subtype(typ, self.chk.named_generic_type(
3954-
'typing.Mapping',
3955-
[self.named_type('builtins.str'),
3956-
AnyType(TypeOfAny.special_form)]))
3957-
or
3958-
is_subtype(typ, self.chk.named_generic_type(
3959-
'typing.Mapping',
3960-
[self.named_type('builtins.unicode'),
3961-
AnyType(TypeOfAny.special_form)])))
3949+
ret = (
3950+
is_subtype(typ, self.chk.named_generic_type('typing.Mapping',
3951+
[self.named_type('builtins.str'), AnyType(TypeOfAny.special_form)])) or
3952+
is_subtype(typ, self.chk.named_generic_type('typing.Mapping',
3953+
[UninhabitedType(), UninhabitedType()])))
3954+
if self.chk.options.python_version[0] < 3:
3955+
ret = ret or is_subtype(typ, self.chk.named_generic_type('typing.Mapping',
3956+
[self.named_type('builtins.unicode'), AnyType(TypeOfAny.special_form)]))
3957+
return ret
39623958

39633959
def has_member(self, typ: Type, member: str) -> bool:
39643960
"""Does type have member with the given name?"""

test-data/unit/check-ctypes.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,6 @@ import ctypes
182182
intarr4 = ctypes.c_int * 4
183183

184184
x = {"a": 1, "b": 2}
185-
intarr4(**x) # E: Too many arguments for "Array"
185+
intarr4(**x)
186186

187187
[builtins fixtures/floatdict.pyi]

test-data/unit/check-kwargs.test

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -299,8 +299,9 @@ d = None # type: Dict[str, A]
299299
f(**d)
300300
f(x=A(), **d)
301301
d2 = None # type: Dict[str, B]
302-
f(**d2) # E: Argument 1 to "f" has incompatible type "**Dict[str, B]"; expected "A"
303-
f(x=A(), **d2) # E: Argument 2 to "f" has incompatible type "**Dict[str, B]"; expected "A"
302+
f(**d2) # E: Argument 1 to "f" has incompatible type "**Dict[str, B]"; expected "A"
303+
f(x=A(), **d2) # E: Argument 2 to "f" has incompatible type "**Dict[str, B]"; expected "A"
304+
f(**{'x': B()}) # E: Argument 1 to "f" has incompatible type "**Dict[str, B]"; expected "A"
304305
class A: pass
305306
class B: pass
306307
[builtins fixtures/dict.pyi]
@@ -396,7 +397,7 @@ class A: pass
396397
from typing import Any, Dict
397398
def f(*args: 'A') -> None: pass
398399
d = None # type: Dict[Any, Any]
399-
f(**d) # E: Too many arguments for "f"
400+
f(**d)
400401
class A: pass
401402
[builtins fixtures/dict.pyi]
402403

@@ -491,3 +492,11 @@ m = {} # type: Mapping[str, object]
491492
f(**m)
492493
g(**m) # TODO: Should be an error
493494
[builtins fixtures/dict.pyi]
495+
496+
[case testPassingEmptyDictWithStars]
497+
def f(): pass
498+
def g(x=1): pass
499+
500+
f(**{})
501+
g(**{})
502+
[builtins fixtures/dict.pyi]

test-data/unit/check-typeddict.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1584,8 +1584,8 @@ d = None # type: Dict[Any, Any]
15841584

15851585
f1(**td, **d)
15861586
f1(**d, **td)
1587-
f2(**td, **d) # E: Too many arguments for "f2"
1588-
f2(**d, **td) # E: Too many arguments for "f2"
1587+
f2(**td, **d)
1588+
f2(**d, **td)
15891589
[builtins fixtures/dict.pyi]
15901590

15911591
[case testTypedDictNonMappingMethods]

0 commit comments

Comments
 (0)