Skip to content

Commit

Permalink
Fix joining of Sequence (e.g. variadic tuple) and fixed-length tuple (#…
Browse files Browse the repository at this point in the history
…8335)

For example:

* Tuple[int] + Tuple[bool, ...] becomes Tuple[int, ...]
* List[int] + Tuple[bool, ...] becomes Sequence[int]

Previously Mypy simply punted and returned `object`.

This solves the other part of issue #4975. Fixes issue #8074.
  • Loading branch information
intgr authored Jan 31, 2020
1 parent 1f9d87e commit d640155
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 3 deletions.
13 changes: 12 additions & 1 deletion mypy/join.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ def visit_instance(self, t: Instance) -> ProperType:
return join_types(t, self.s)
elif isinstance(self.s, TypedDictType):
return join_types(t, self.s)
elif isinstance(self.s, TupleType):
return join_types(t, self.s)
elif isinstance(self.s, LiteralType):
return join_types(t, self.s)
else:
Expand Down Expand Up @@ -260,6 +262,15 @@ def visit_overloaded(self, t: Overloaded) -> ProperType:
return join_types(t.fallback, s)

def visit_tuple_type(self, t: TupleType) -> ProperType:
# When given two fixed-length tuples:
# * If they have the same length, join their subtypes item-wise:
# Tuple[int, bool] + Tuple[bool, bool] becomes Tuple[int, bool]
#
# Otherwise, `t` is a fixed-length tuple but `self.s` is NOT:
# * Joining with a variadic tuple returns variadic tuple:
# Tuple[int, bool] + Tuple[bool, ...] becomes Tuple[int, ...]
# * Joining with any Sequence also returns a Sequence:
# Tuple[int, bool] + List[bool] becomes Sequence[int]
if isinstance(self.s, TupleType) and self.s.length() == t.length():
items = [] # type: List[Type]
for i in range(t.length()):
Expand All @@ -269,7 +280,7 @@ def visit_tuple_type(self, t: TupleType) -> ProperType:
assert isinstance(fallback, Instance)
return TupleType(items, fallback)
else:
return self.default(self.s)
return join_types(self.s, mypy.typeops.tuple_fallback(t))

def visit_typeddict_type(self, t: TypedDictType) -> ProperType:
if isinstance(self.s, TypedDictType):
Expand Down
19 changes: 17 additions & 2 deletions mypy/test/testtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,10 +501,21 @@ def test_tuples(self) -> None:

self.assert_join(self.tuple(self.fx.a, self.fx.a),
self.fx.std_tuple,
self.fx.o)
self.var_tuple(self.fx.anyt))
self.assert_join(self.tuple(self.fx.a),
self.tuple(self.fx.a, self.fx.a),
self.fx.o)
self.var_tuple(self.fx.a))

def test_var_tuples(self) -> None:
self.assert_join(self.tuple(self.fx.a),
self.var_tuple(self.fx.a),
self.var_tuple(self.fx.a))
self.assert_join(self.var_tuple(self.fx.a),
self.tuple(self.fx.a),
self.var_tuple(self.fx.a))
self.assert_join(self.var_tuple(self.fx.a),
self.tuple(),
self.var_tuple(self.fx.a))

def test_function_types(self) -> None:
self.assert_join(self.callable(self.fx.a, self.fx.b),
Expand Down Expand Up @@ -760,6 +771,10 @@ def assert_simple_join(self, s: Type, t: Type, join: Type) -> None:
def tuple(self, *a: Type) -> TupleType:
return TupleType(list(a), self.fx.std_tuple)

def var_tuple(self, t: Type) -> Instance:
"""Construct a variable-length tuple type"""
return Instance(self.fx.std_tuplei, [t])

def callable(self, *a: Type) -> CallableType:
"""callable(a1, ..., an, r) constructs a callable with argument types
a1, ... an and return type r.
Expand Down
81 changes: 81 additions & 0 deletions test-data/unit/check-tuples.test
Original file line number Diff line number Diff line change
Expand Up @@ -1077,6 +1077,87 @@ x, y = g(z) # E: Argument 1 to "g" has incompatible type "int"; expected "Tuple[
[builtins fixtures/tuple.pyi]
[out]

[case testFixedTupleJoinVarTuple]
from typing import Tuple

class A: pass
class B(A): pass

fixtup = None # type: Tuple[B, B]

vartup_b = None # type: Tuple[B, ...]
reveal_type(fixtup if int() else vartup_b) # N: Revealed type is 'builtins.tuple[__main__.B]'
reveal_type(vartup_b if int() else fixtup) # N: Revealed type is 'builtins.tuple[__main__.B]'

vartup_a = None # type: Tuple[A, ...]
reveal_type(fixtup if int() else vartup_a) # N: Revealed type is 'builtins.tuple[__main__.A]'
reveal_type(vartup_a if int() else fixtup) # N: Revealed type is 'builtins.tuple[__main__.A]'


[builtins fixtures/tuple.pyi]
[out]

[case testFixedTupleJoinList]
from typing import Tuple, List

class A: pass
class B(A): pass

fixtup = None # type: Tuple[B, B]

lst_b = None # type: List[B]
reveal_type(fixtup if int() else lst_b) # N: Revealed type is 'typing.Sequence[__main__.B]'
reveal_type(lst_b if int() else fixtup) # N: Revealed type is 'typing.Sequence[__main__.B]'

lst_a = None # type: List[A]
reveal_type(fixtup if int() else lst_a) # N: Revealed type is 'typing.Sequence[__main__.A]'
reveal_type(lst_a if int() else fixtup) # N: Revealed type is 'typing.Sequence[__main__.A]'

[builtins fixtures/tuple.pyi]
[out]

[case testEmptyTupleJoin]
from typing import Tuple, List

class A: pass

empty = ()

fixtup = None # type: Tuple[A]
reveal_type(fixtup if int() else empty) # N: Revealed type is 'builtins.tuple[__main__.A]'
reveal_type(empty if int() else fixtup) # N: Revealed type is 'builtins.tuple[__main__.A]'

vartup = None # type: Tuple[A, ...]
reveal_type(empty if int() else vartup) # N: Revealed type is 'builtins.tuple[__main__.A]'
reveal_type(vartup if int() else empty) # N: Revealed type is 'builtins.tuple[__main__.A]'

lst = None # type: List[A]
reveal_type(empty if int() else lst) # N: Revealed type is 'typing.Sequence[__main__.A*]'
reveal_type(lst if int() else empty) # N: Revealed type is 'typing.Sequence[__main__.A*]'

[builtins fixtures/tuple.pyi]
[out]

[case testTupleSubclassJoin]
from typing import Tuple, NamedTuple

class NTup(NamedTuple):
a: bool
b: bool

class SubTuple(Tuple[bool]): ...
class SubVarTuple(Tuple[int, ...]): ...

ntup = None # type: NTup
subtup = None # type: SubTuple
vartup = None # type: SubVarTuple

reveal_type(ntup if int() else vartup) # N: Revealed type is 'builtins.tuple[builtins.int]'
reveal_type(subtup if int() else vartup) # N: Revealed type is 'builtins.tuple[builtins.int]'

[builtins fixtures/tuple.pyi]
[out]

[case testTupleWithUndersizedContext]
a = ([1], 'x')
if int():
Expand Down

0 comments on commit d640155

Please sign in to comment.