Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix joining of Sequence (e.g. variadic tuple) and fixed-length tuple #8335

Merged
merged 1 commit into from
Jan 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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