Repository: python/mypy

Issue:
Empty list incorrectly inferred under --strict-optional
Repro:

``` python
from typing import *

def f() -> Optional[Tuple[str, List[str]]]:
  return '', []
```

This is OK without `--strict-optional`, but with it, it says

```
__tmp__.py: note: In function "f":
__tmp__.py:4: error: Incompatible return value type (got "Tuple[str, List[object]]", expected "Optional[Tuple[str, List[str]]]")
```



Discussion:
This can also be repro'd with:

``` py
def f() -> Union[int, Tuple[List[str]]]:
    return ([],)
```

It looks like we don't propagate inference through unions.  This might be a bit tricky.

Interestingly, this also doesn't work:

``` py
def f() -> Tuple[List[str], ...]:
    return ([],)
```


Resolving patch:
diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -6,7 +6,7 @@
     Type, AnyType, CallableType, Overloaded, NoneTyp, Void, TypeVarDef,
     TupleType, Instance, TypeVarId, TypeVarType, ErasedType, UnionType,
     PartialType, DeletedType, UnboundType, UninhabitedType, TypeType,
-    true_only, false_only
+    true_only, false_only, is_named_instance
 )
 from mypy.nodes import (
     NameExpr, RefExpr, Var, FuncDef, OverloadedFuncDef, TypeInfo, CallExpr,
@@ -1371,11 +1371,26 @@ def check_lst_expr(self, items: List[Node], fullname: str,
 
     def visit_tuple_expr(self, e: TupleExpr) -> Type:
         """Type check a tuple expression."""
-        ctx = None  # type: TupleType
         # Try to determine type context for type inference.
-        if isinstance(self.chk.type_context[-1], TupleType):
-            t = self.chk.type_context[-1]
-            ctx = t
+        type_context = self.chk.type_context[-1]
+        type_context_items = None
+        if isinstance(type_context, UnionType):
+            tuples_in_context = [t for t in type_context.items
+                                 if (isinstance(t, TupleType) and len(t.items) == len(e.items)) or
+                                 is_named_instance(t, 'builtins.tuple')]
+            if len(tuples_in_context) == 1:
+                type_context = tuples_in_context[0]
+            else:
+                # There are either no relevant tuples in the Union, or there is
+                # more than one.  Either way, we can't decide on a context.
+                pass
+
+        if isinstance(type_context, TupleType):
+            type_context_items = type_context.items
+        elif is_named_instance(type_context, 'builtins.tuple'):
+            assert isinstance(type_context, Instance)
+            if type_context.args:
+                type_context_items = [type_context.args[0]] * len(e.items)
         # NOTE: it's possible for the context to have a different
         # number of items than e.  In that case we use those context
         # items that match a position in e, and we'll worry about type
@@ -1384,7 +1399,7 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type:
         # Infer item types.  Give up if there's a star expression
         # that's not a Tuple.
         items = []  # type: List[Type]
-        j = 0  # Index into ctx.items; irrelevant if ctx is None.
+        j = 0  # Index into type_context_items; irrelevant if type_context_items is none
         for i in range(len(e.items)):
             item = e.items[i]
             tt = None  # type: Type
@@ -1404,10 +1419,10 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type:
                     # Treat the whole thing as a variable-length tuple.
                     return self.check_lst_expr(e.items, 'builtins.tuple', '<tuple>', e)
             else:
-                if not ctx or j >= len(ctx.items):
+                if not type_context_items or j >= len(type_context_items):
                     tt = self.accept(item)
                 else:
-                    tt = self.accept(item, ctx.items[j])
+                    tt = self.accept(item, type_context_items[j])
                     j += 1
                 self.check_usable_type(tt, e)
                 items.append(tt)


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
@@ -808,3 +808,15 @@ a = (1, [])  # E: Incompatible types in assignment (expression has type "Tuple[i
 [case testTupleWithoutContext]
 a = (1, [])  # E: Need type annotation for variable
 [builtins fixtures/tuple.pyi]
+
+[case testTupleWithUnionContext]
+from typing import List, Union, Tuple
+def f() -> Union[int, Tuple[List[str]]]:
+    return ([],)
+[builtins fixtures/tuple.pyi]
+
+[case testTupleWithVariableSizedTupleContext]
+from typing import List, Tuple
+def f() -> Tuple[List[str], ...]:
+    return ([],)
+[builtins fixtures/tuple.pyi]