Repository: python/mypy

Issue:
Crash on incompatible dictionary unpack
Type checking this code fragment causes a mypy crash:

```py
from typing import Dict

def f() -> Dict[int, str]:
    return {1: '', **d()}

def d() -> Dict[int, int]:
    return {}
```

Here's the traceback:

```
Traceback (most recent call last):
  File "/Users/jukka/src/mypy/scripts/mypy", line 6, in <module>
    main(__file__)
  File "/Users/jukka/src/mypy/mypy/main.py", line 88, in main
    res = type_check_only(sources, bin_dir, options, flush_errors)  # noqa
  File "/Users/jukka/src/mypy/mypy/main.py", line 143, in type_check_only
    flush_errors=flush_errors)
  File "/Users/jukka/src/mypy/mypy/build.py", line 183, in build
    flush_errors, fscache)
  File "/Users/jukka/src/mypy/mypy/build.py", line 268, in _build
    graph = dispatch(sources, manager)
  File "/Users/jukka/src/mypy/mypy/build.py", line 2253, in dispatch
    process_graph(graph, manager)
  File "/Users/jukka/src/mypy/mypy/build.py", line 2543, in process_graph
    process_stale_scc(graph, scc, manager)
  File "/Users/jukka/src/mypy/mypy/build.py", line 2659, in process_stale_scc
    graph[id].type_check_first_pass()
  File "/Users/jukka/src/mypy/mypy/build.py", line 1903, in type_check_first_pass
    self.type_checker().check_first_pass()
  File "/Users/jukka/src/mypy/mypy/checker.py", line 252, in check_first_pass
    self.accept(d)
  File "/Users/jukka/src/mypy/mypy/checker.py", line 351, in accept
    stmt.accept(self)
  File "/Users/jukka/src/mypy/mypy/nodes.py", line 539, in accept
    return visitor.visit_func_def(self)
  File "/Users/jukka/src/mypy/mypy/checker.py", line 607, in visit_func_def
    self._visit_func_def(defn)
  File "/Users/jukka/src/mypy/mypy/checker.py", line 611, in _visit_func_def
    self.check_func_item(defn, name=defn.name())
  File "/Users/jukka/src/mypy/mypy/checker.py", line 671, in check_func_item
    self.check_func_def(defn, typ, name)
  File "/Users/jukka/src/mypy/mypy/checker.py", line 834, in check_func_def
    self.accept(item.body)
  File "/Users/jukka/src/mypy/mypy/checker.py", line 351, in accept
    stmt.accept(self)
  File "/Users/jukka/src/mypy/mypy/nodes.py", line 804, in accept
    return visitor.visit_block(self)
  File "/Users/jukka/src/mypy/mypy/checker.py", line 1433, in visit_block
    self.accept(s)
  File "/Users/jukka/src/mypy/mypy/checker.py", line 351, in accept
    stmt.accept(self)
  File "/Users/jukka/src/mypy/mypy/nodes.py", line 919, in accept
    return visitor.visit_return_stmt(self)
  File "/Users/jukka/src/mypy/mypy/checker.py", line 2204, in visit_return_stmt
    self.check_return_stmt(s)
  File "/Users/jukka/src/mypy/mypy/checker.py", line 2235, in check_return_stmt
    allow_none_return=allow_none_func_call)
  File "/Users/jukka/src/mypy/mypy/checkexpr.py", line 2379, in accept
    typ = node.accept(self)
  File "/Users/jukka/src/mypy/mypy/nodes.py", line 1570, in accept
    return visitor.visit_dict_expr(self)
  File "/Users/jukka/src/mypy/mypy/checkexpr.py", line 2018, in visit_dict_expr
    self.check_call(method, [arg], [nodes.ARG_POS], arg)
  File "/Users/jukka/src/mypy/mypy/checkexpr.py", line 620, in check_call
    object_type=object_type)
  File "/Users/jukka/src/mypy/mypy/checkexpr.py", line 587, in check_call
    messages=arg_messages)
  File "/Users/jukka/src/mypy/mypy/checkexpr.py", line 1061, in check_argument_types
    actual + 1, i + 1, callee, context, messages)
  File "/Users/jukka/src/mypy/mypy/checkexpr.py", line 1097, in check_arg
    caller_kind, context)
  File "/Users/jukka/src/mypy/mypy/messages.py", line 643, in incompatible_argument
    arg_name = context.arg_names[n - 1]
IndexError: list index out of range
```


Discussion:

Resolving patch:
diff --git a/mypy/messages.py b/mypy/messages.py
--- a/mypy/messages.py
+++ b/mypy/messages.py
@@ -645,7 +645,7 @@ def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type:
             # For function calls with keyword arguments, display the argument name rather than the
             # number.
             arg_label = str(n)
-            if isinstance(context, CallExpr):
+            if isinstance(context, CallExpr) and len(context.arg_names) >= n:
                 arg_name = context.arg_names[n - 1]
                 if arg_name is not None:
                     arg_label = '"{}"'.format(arg_name)
diff --git a/mypy/server/deps.py b/mypy/server/deps.py
--- a/mypy/server/deps.py
+++ b/mypy/server/deps.py
@@ -376,7 +376,8 @@ def process_lvalue(self, lvalue: Expression) -> None:
         elif isinstance(lvalue, TupleExpr):
             for item in lvalue.items:
                 self.process_lvalue(item)
-        # TODO: star lvalue
+        elif isinstance(lvalue, StarExpr):
+            self.process_lvalue(lvalue.expr)
 
     def is_self_member_ref(self, memberexpr: MemberExpr) -> bool:
         """Does memberexpr to refer to an attribute of self?"""


Test patch:
diff --git a/test-data/unit/deps.test b/test-data/unit/deps.test
--- a/test-data/unit/deps.test
+++ b/test-data/unit/deps.test
@@ -1109,3 +1109,20 @@ class A(B):
 <mod.C.__new__> -> <m.D.__new__>
 <mod.C.x> -> <m.D.x>
 <mod.C> -> m, m.D
+
+[case testIndexedStarLvalue]
+from typing import List, Tuple
+
+class B:
+    def __setitem__(self, i: int, v: List[str]) -> None: pass
+
+def g() -> Tuple[int, str, str]: pass
+
+def f(b: B) -> None:
+    a, *b[0] = g()
+[builtins fixtures/list.pyi]
+[out]
+<m.B.__getitem__> -> m.f
+<m.B.__setitem__> -> m.f
+<m.B> -> <m.f>, m.B, m.f
+<m.g> -> m.f
diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test
--- a/test-data/unit/fine-grained.test
+++ b/test-data/unit/fine-grained.test
@@ -34,6 +34,11 @@
 --
 -- Test runner can parse options from mypy.ini file. Updating this file in between
 -- incremental runs is not yet supported.
+--
+-- Each test case run without caching and with caching (if the initial run passes),
+-- unless it has one a --skip-cache or --skip-nocache suffix. We sometimes
+-- skip caching test cases to speed up tests, if the caching variant is not useful.
+-- The caching test case variants get an implicit _cached suffix.
 
 [case testReprocessFunction]
 import m
@@ -5801,6 +5806,90 @@ class M(type):
 ==
 a.py:2: error: Argument 1 to "f" of "M" has incompatible type "int"; expected "str"
 
+[case testExtendedUnpacking-skip-cache]
+from typing import List
+from a import g
+def f() -> List[int]:
+    a, *b = g()
+    return b
+
+[file a.py]
+from typing import Tuple
+def g() -> Tuple[str, int, int]: pass
+
+[file a.py.2]
+from typing import Tuple
+def g() -> Tuple[str, str]: pass
+
+[builtins fixtures/tuple.pyi]
+[out]
+==
+main:5: error: Incompatible return value type (got "List[str]", expected "List[int]")
+
+[case testUnpackInExpression1-skip-cache]
+from typing import Tuple, List
+from a import t
+
+def f() -> Tuple[int, int]:
+    return (1, *t())
+
+def g() -> List[int]:
+    return [1, *t()]
+
+[file a.py]
+from typing import Tuple
+def t() -> Tuple[int]: ...
+
+[file a.py.2]
+from typing import Tuple
+def t() -> Tuple[str]: ...
+
+[builtins fixtures/list.pyi]
+[out]
+==
+main:5: error: Incompatible return value type (got "Tuple[int, str]", expected "Tuple[int, int]")
+main:8: error: List item 1 has incompatible type "Tuple[str]"; expected "int"
+
+[case testUnpackInExpression2-skip-cache]
+from typing import Set
+from a import t
+
+def f() -> Set[int]:
+    return {1, *t()}
+
+[file a.py]
+from typing import Tuple
+def t() -> Tuple[int]: pass
+
+[file a.py.2]
+from typing import Tuple
+def t() -> Tuple[str]: pass
+
+[builtins fixtures/set.pyi]
+[out]
+==
+main:5: error: Argument 2 to <set> has incompatible type "*Tuple[str]"; expected "int"
+
+[case testUnpackInExpression3-skip-cache]
+from typing import Dict
+from a import d
+
+def f() -> Dict[int, str]:
+    return {1: '', **d()}
+
+[file a.py]
+from typing import Dict
+def d() -> Dict[int, str]: pass
+
+[file a.py.2]
+from typing import Dict
+def d() -> Dict[int, int]: pass
+
+[builtins fixtures/dict.pyi]
+[out]
+==
+main:5: error: Argument 1 to "update" of "dict" has incompatible type "Dict[int, int]"; expected "Mapping[int, str]"
+
 [case testAwaitAndAsyncDef-skip-cache]
 from a import g
 
diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test
--- a/test-data/unit/pythoneval.test
+++ b/test-data/unit/pythoneval.test
@@ -1259,3 +1259,14 @@ class B:
 [out]
 _testInvalidSlots.py:2: error: Incompatible types in assignment (expression has type "int", base class "object" defined the type as "Union[str, Iterable[str], None]")
 _testInvalidSlots.py:4: error: Incompatible types in assignment (expression has type "Tuple[int, int]", base class "object" defined the type as "Union[str, Iterable[str], None]")
+
+[case testDictWithStarStarSpecialCase]
+from typing import Dict
+
+def f() -> Dict[int, str]:
+    return {1: '', **d()}
+
+def d() -> Dict[int, int]:
+    return {}
+[out]
+_testDictWithStarStarSpecialCase.py:4: error: Argument 1 to "update" of "dict" has incompatible type "Dict[int, int]"; expected "Mapping[int, str]"