Repository: python/mypy

Issue:
**kwargs checked against wrong type with defaults or *args
```
def a(*args: str, **kw: int) -> None:
    b(**kw)
    b('', **kw)
    c(**kw)
    c('', **kw)

def b(args: str='', **kw: int) -> None:
    pass

def c(*args: str, **kw: int) -> None:
    pass
```

results, with 0.4.3:

```
/tmp/minimize.py: note: In function "a":
/tmp/minimize.py:2: error: Argument 1 to "b" has incompatible type **Dict[str, int]; expected "str"
/tmp/minimize.py:4: error: Argument 1 to "c" has incompatible type **Dict[str, int]; expected "str"
```



Discussion:
Can you try again with the latest revision from the repo? I fixed some things in this area recently.

Never mind. I can still repro this.

The logic the code might be implementing here, at least in the call to `b`, is that it's possible 'args' is a key in `kw`, in which case we'd be passing an `int` to a parameter of type `str`.

That logic doesn't seem to apply in the second case, though, which would make that a bug.

In the `b` case it's possible this is the kind of unsoundness we'd rather allow, because it's probably cumbersome to explicitly prevent it in this code. Even if we do give an error for it, this error message isn't really on point about what the issue is, and if nothing else we should make it clearer.

And thanks, Karl, for the report!

In particular, calling `a(args=0)` would end up calling `b(args=0)` which is a bug in your code. But the same reasoning doesn't apply to `c(**kw)`, so that's a bug in mypy.

This can be fixed by adding at [checkexpr.py#L1980](https://github.com/python/mypy/blob/master/mypy/checkexpr.py#L1890) (right below the comment referring to that situation)

```
-    if ((callee_names[j] and no_certain_match)
                    or callee_kinds[j] == nodes.ARG_STAR2):
+    if ((callee_names[j] and no_certain_match and callee_kinds[j] != nodes.ARG_STAR)
                    or callee_kinds[j] == nodes.ARG_STAR2):
```

However, it raises another error for the following file. I wonder whether this is the expected behavior:

```
def a(**kw: int) -> None:
    a(**kw)
    c(**kw)
    d(**kw)

def b(a: int) -> None: pass
def c(*args: str) -> None: pass
def d() -> None: pass

def x(*args: int) -> None:
    y(*args)
    z(*args)
    q(*args)

def y() -> None: pass
def z(**kw: int) -> None: pass
def q(a: int) -> None: pass
```

Result:

```
tmp.py: note: In function "a":
tmp.py:3: error: Too many arguments for "c"
tmp.py:4: error: Too many arguments for "d"
tmp.py: note: In function "x":
tmp.py:16: error: Too many arguments for "y"
tmp.py:17: error: Too many arguments for "z"
```

Perhaps that's intentional, but I don't see why the empty `*args` and the empty `**kw` are discriminated.
The situation with `*args` is the current behavior, irrelevant to the change. It's explicitly checked for [in the code](https://github.com/python/mypy/blob/master/mypy/checkexpr.py#L634) for being an empty tuple, emitting error otherwise. why?

If the suggested solution seems valid, I can open a PR.

(This appeared two more times so raise priority to normal)
https://github.com/python/mypy/issues/5834 has another example of this with some details.
Raised priority to high, since this keeps coming.

As noticed in #6092 this bug only appears when the type of `kwargs` is `Dict`, but it doesn't when the type is `Mapping`.
I think that I know how to fix this since I just recently worked on the relevant code.

Resolving patch:
diff --git a/mypy/argmap.py b/mypy/argmap.py
--- a/mypy/argmap.py
+++ b/mypy/argmap.py
@@ -81,8 +81,10 @@ def map_actuals_to_formals(actual_kinds: List[int],
                     no_certain_match = (
                         not formal_to_actual[fi]
                         or actual_kinds[formal_to_actual[fi][0]] == nodes.ARG_STAR)
-                    if ((formal_names[fi] and no_certain_match)
-                            or formal_kinds[fi] == nodes.ARG_STAR2):
+                    if ((formal_names[fi]
+                            and no_certain_match
+                            and formal_kinds[fi] != nodes.ARG_STAR) or
+                            formal_kinds[fi] == nodes.ARG_STAR2):
                         formal_to_actual[fi].append(ai)
     return formal_to_actual
 


Test patch:
diff --git a/test-data/unit/check-kwargs.test b/test-data/unit/check-kwargs.test
--- a/test-data/unit/check-kwargs.test
+++ b/test-data/unit/check-kwargs.test
@@ -403,3 +403,21 @@ class A:
 tmp/m.py:1: error: Unsupported operand types for + ("int" and "str")
 main:2: error: Unexpected keyword argument "x" for "A"
 tmp/m.py:3: note: "A" defined here
+
+[case testStarArgsAndKwArgsSpecialCase]
+from typing import Dict, Mapping
+
+def f(*vargs: int, **kwargs: object) -> None:
+	pass
+
+def g(arg: int = 0, **kwargs: object) -> None:
+	pass
+
+d = {} # type: Dict[str, object]
+f(**d)
+g(**d)  # E: Argument 1 to "g" has incompatible type "**Dict[str, object]"; expected "int"
+
+m = {} # type: Mapping[str, object]
+f(**m)
+g(**m)  # TODO: Should be an error
+[builtins fixtures/dict.pyi]