Repository: python/mypy

Issue:
Unpacking starred expressions in tuples
(migrated from [a comment](https://github.com/python/mypy/issues/1178#issuecomment-545327224) in #1178 at @ethanhs's request)

There seems to an issue with unpacking things when they are part of a tuple on the right side of assignment. #1178 is related, but specifically deals with variable-length tuples (and using `len()` to distinguish them). The current issue occurs even for fixed-length and type-annotated tuples.

```python
# file: ex.py
from typing import Tuple
points = (1, 2)  # type: Tuple[int, int]
x, y, z = *points, 0
```

This results in incorrect inference about the tuple size (but Python does not complain, only mypy):

```console
$ mypy ex.py 
ex.py:4: error: Need more than 2 values to unpack (3 expected)
Found 1 error in 1 file (checked 1 source file)
```

A workaround is to use cast(), as @JukkaL [suggested](https://github.com/python/mypy/issues/1178#issuecomment-176185607):

```python
x, y, z = cast(Tuple[int, int, int], (*points, 0))
```

And here's my versions of Python and mypy:

```console
$ mypy --version
mypy 0.740
$ python --version
Python 3.7.3
```




Discussion:
It looks like the issue is in `check_assignment_to_multiple_lvalues`, where it has a case for a tuple RHS but doesn't take into account star exprs.

This is a decent starter issue, maybe, if someone wants it.
@msullivan I'd be happy to give this a shot
Update: I added a test to `test-data/unit/check-tuples.test`:

```
[case testTupleWithStarExpr5]
a = (1, 2)
b, c = (*a, 3)  # E: Too many values to unpack (2 expected, 3 provided)
b, c, d = (*a, 3)
b, c, d, e = (*a, 3)  # E: Need more than 3 values to unpack (2 expected)
[builtins fixtures/tuple.pyi]
```

The test fails as expected. But now I'm stuck in `check_assignment_to_multiple_lvalues` trying to unpack the starred expression to get its inner types. I tried `flatten(x)` and `x.expr.node.type.items` but these aren't quite right. ~~Are there developer docs for working with these things or can I get some hints on how to proceed?~~ (edit: I found the [developer docs](https://github.com/python/mypy/wiki/Developer-Guides), at least)
Ok, I'm still learning about mypy internals but here's where I'm at: I can get an accurate type signature for the right hand side of assignment (even for silly examples with nested starred-expressions like `*(*a, 3), 4`) with `self.expr_checker.accept(rvalue)` (in `TypeChecker.check_assignment_to_multiple_lvalues()`), and this passes the check for the appropriate number of items on the left and right side, but then it fails when it tries to call `self.check_assignment()` on each paired left and right value. I think this is because the function expects an AST expression and I'm giving it a resolved type. Also even if I could give the function the original expressions I don't think they would help because of examples like `x, *y = *a, b` (which [crashes mypy](https://github.com/python/mypy/issues/3825), btw), where the assignment crosses tree branches (y gets `a[1:] + [b]`).

I'm not sure if there's a more straightforward way of doing this. If it's obvious to anyone else, any hints as to what I'm missing would be appreciated.
I'm not currently working on this anymore. Just mentioning this in case someone else wants to take it on.
I would like to work on this issue. Any pointers to keep in mind?

>  I think this is because the function expects an AST expression and I'm giving it a resolved type. 

`TempNode` may help in cases like this. It can be used to create a dummy AST node with some type.

Resolving patch:
diff --git a/mypy/checker.py b/mypy/checker.py
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -2482,8 +2482,47 @@ def check_assignment_to_multiple_lvalues(self, lvalues: List[Lvalue], rvalue: Ex
             # using the type of rhs, because this allowed more fine grained
             # control in cases like: a, b = [int, str] where rhs would get
             # type List[object]
-
-            rvalues = rvalue.items
+            rvalues = []  # type: List[Expression]
+            iterable_type = None  # type: Optional[Type]
+            last_idx = None  # type: Optional[int]
+            for idx_rval, rval in enumerate(rvalue.items):
+                if isinstance(rval, StarExpr):
+                    typs = get_proper_type(self.expr_checker.visit_star_expr(rval).type)
+                    if isinstance(typs, TupleType):
+                        rvalues.extend([TempNode(typ) for typ in typs.items])
+                    elif self.type_is_iterable(typs) and isinstance(typs, Instance):
+                        if (iterable_type is not None
+                                and iterable_type != self.iterable_item_type(typs)):
+                            self.fail("Contiguous iterable with same type expected", context)
+                        else:
+                            if last_idx is None or last_idx + 1 == idx_rval:
+                                rvalues.append(rval)
+                                last_idx = idx_rval
+                                iterable_type = self.iterable_item_type(typs)
+                            else:
+                                self.fail("Contiguous iterable with same type expected", context)
+                    else:
+                        self.fail("Invalid type '{}' for *expr (iterable expected)".format(typs),
+                             context)
+                else:
+                    rvalues.append(rval)
+            iterable_start = None  # type: Optional[int]
+            iterable_end = None  # type: Optional[int]
+            for i, rval in enumerate(rvalues):
+                if isinstance(rval, StarExpr):
+                    typs = get_proper_type(self.expr_checker.visit_star_expr(rval).type)
+                    if self.type_is_iterable(typs) and isinstance(typs, Instance):
+                        if iterable_start is None:
+                            iterable_start = i
+                        iterable_end = i
+            if (iterable_start is not None
+                    and iterable_end is not None
+                    and iterable_type is not None):
+                iterable_num = iterable_end - iterable_start + 1
+                rvalue_needed = len(lvalues) - (len(rvalues) - iterable_num)
+                if rvalue_needed > 0:
+                    rvalues = rvalues[0: iterable_start] + [TempNode(iterable_type)
+                        for i in range(rvalue_needed)] + rvalues[iterable_end + 1:]
 
             if self.check_rvalue_count_in_assignment(lvalues, len(rvalues), context):
                 star_index = next((i for i, lv in enumerate(lvalues) if


Test patch:
diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test
--- a/test-data/unit/check-tuples.test
+++ b/test-data/unit/check-tuples.test
@@ -1421,3 +1421,40 @@ t6: Tuple[int, int, int, int, int, int, int, int, int, int, int, int] = (1, 2, 3
                                                                     # E: Incompatible types in assignment (expression has type Tuple[int, int, ... <15 more items>], variable has type Tuple[int, int, ... <10 more items>])
 
 [builtins fixtures/tuple.pyi]
+
+[case testTupleWithStarExpr]
+from typing import Tuple, List
+points = (1, "test")  # type: Tuple[int, str]
+x, y, z = *points, 0
+reveal_type(x) # N: Revealed type is 'builtins.int'
+reveal_type(y) # N: Revealed type is 'builtins.str'
+reveal_type(z) # N: Revealed type is 'builtins.int'
+
+points2 = [1,2]
+x2, y2, z2= *points2, "test"
+
+reveal_type(x2) # N: Revealed type is 'builtins.int*'
+reveal_type(y2) # N: Revealed type is 'builtins.int*'
+reveal_type(z2) # N: Revealed type is 'builtins.str'
+
+x3, x4, y3, y4, z3 = *points, *points2, "test"
+
+reveal_type(x3) # N: Revealed type is 'builtins.int'
+reveal_type(x4) # N: Revealed type is 'builtins.str'
+reveal_type(y3) # N: Revealed type is 'builtins.int*'
+reveal_type(y4) # N: Revealed type is 'builtins.int*'
+reveal_type(z3) # N: Revealed type is 'builtins.str'
+
+x5, x6, y5, y6, z4 = *points2, *points2, "test"
+
+reveal_type(x5) # N: Revealed type is 'builtins.int*'
+reveal_type(x6) # N: Revealed type is 'builtins.int*'
+reveal_type(y5) # N: Revealed type is 'builtins.int*'
+reveal_type(y6) # N: Revealed type is 'builtins.int*'
+reveal_type(z4) # N: Revealed type is 'builtins.str'
+
+points3 = ["test1", "test2"]
+x7, x8, y7, y8 = *points2, *points3 # E: Contiguous iterable with same type expected
+
+x9, y9, x10, y10, z5 = *points2, 1, *points2 # E: Contiguous iterable with same type expected
+[builtins fixtures/tuple.pyi]