Skip to content

Commit f8374bd

Browse files
ddfisherJukkaL
authored andcommitted
Pass inference through Unions of tuples and variable length tuples (#2076)
Fix #2074.
1 parent c0f2103 commit f8374bd

File tree

2 files changed

+35
-8
lines changed

2 files changed

+35
-8
lines changed

mypy/checkexpr.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
Type, AnyType, CallableType, Overloaded, NoneTyp, Void, TypeVarDef,
77
TupleType, Instance, TypeVarId, TypeVarType, ErasedType, UnionType,
88
PartialType, DeletedType, UnboundType, UninhabitedType, TypeType,
9-
true_only, false_only
9+
true_only, false_only, is_named_instance
1010
)
1111
from mypy.nodes import (
1212
NameExpr, RefExpr, Var, FuncDef, OverloadedFuncDef, TypeInfo, CallExpr,
@@ -1408,11 +1408,26 @@ def check_lst_expr(self, items: List[Node], fullname: str,
14081408

14091409
def visit_tuple_expr(self, e: TupleExpr) -> Type:
14101410
"""Type check a tuple expression."""
1411-
ctx = None # type: TupleType
14121411
# Try to determine type context for type inference.
1413-
if isinstance(self.chk.type_context[-1], TupleType):
1414-
t = self.chk.type_context[-1]
1415-
ctx = t
1412+
type_context = self.chk.type_context[-1]
1413+
type_context_items = None
1414+
if isinstance(type_context, UnionType):
1415+
tuples_in_context = [t for t in type_context.items
1416+
if (isinstance(t, TupleType) and len(t.items) == len(e.items)) or
1417+
is_named_instance(t, 'builtins.tuple')]
1418+
if len(tuples_in_context) == 1:
1419+
type_context = tuples_in_context[0]
1420+
else:
1421+
# There are either no relevant tuples in the Union, or there is
1422+
# more than one. Either way, we can't decide on a context.
1423+
pass
1424+
1425+
if isinstance(type_context, TupleType):
1426+
type_context_items = type_context.items
1427+
elif is_named_instance(type_context, 'builtins.tuple'):
1428+
assert isinstance(type_context, Instance)
1429+
if type_context.args:
1430+
type_context_items = [type_context.args[0]] * len(e.items)
14161431
# NOTE: it's possible for the context to have a different
14171432
# number of items than e. In that case we use those context
14181433
# items that match a position in e, and we'll worry about type
@@ -1421,7 +1436,7 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type:
14211436
# Infer item types. Give up if there's a star expression
14221437
# that's not a Tuple.
14231438
items = [] # type: List[Type]
1424-
j = 0 # Index into ctx.items; irrelevant if ctx is None.
1439+
j = 0 # Index into type_context_items; irrelevant if type_context_items is none
14251440
for i in range(len(e.items)):
14261441
item = e.items[i]
14271442
tt = None # type: Type
@@ -1441,10 +1456,10 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type:
14411456
# Treat the whole thing as a variable-length tuple.
14421457
return self.check_lst_expr(e.items, 'builtins.tuple', '<tuple>', e)
14431458
else:
1444-
if not ctx or j >= len(ctx.items):
1459+
if not type_context_items or j >= len(type_context_items):
14451460
tt = self.accept(item)
14461461
else:
1447-
tt = self.accept(item, ctx.items[j])
1462+
tt = self.accept(item, type_context_items[j])
14481463
j += 1
14491464
self.check_usable_type(tt, e)
14501465
items.append(tt)

test-data/unit/check-tuples.test

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,3 +808,15 @@ a = (1, []) # E: Incompatible types in assignment (expression has type "Tuple[i
808808
[case testTupleWithoutContext]
809809
a = (1, []) # E: Need type annotation for variable
810810
[builtins fixtures/tuple.pyi]
811+
812+
[case testTupleWithUnionContext]
813+
from typing import List, Union, Tuple
814+
def f() -> Union[int, Tuple[List[str]]]:
815+
return ([],)
816+
[builtins fixtures/tuple.pyi]
817+
818+
[case testTupleWithVariableSizedTupleContext]
819+
from typing import List, Tuple
820+
def f() -> Tuple[List[str], ...]:
821+
return ([],)
822+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)