From 580ee545a4493a032c80ccae4855bcac87a51986 Mon Sep 17 00:00:00 2001 From: Theodore Liu Date: Mon, 6 Jan 2020 21:28:53 -0800 Subject: [PATCH 1/3] Initial attempt --- mypy/join.py | 8 ++++++++ test-data/unit/check-inference.test | 27 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/mypy/join.py b/mypy/join.py index a2513bd36201..f7f9abe2d0ed 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -179,6 +179,8 @@ def visit_instance(self, t: Instance) -> ProperType: return join_types(t, self.s) elif isinstance(self.s, LiteralType): return join_types(t, self.s) + elif isinstance(self.s, TupleType): + return join_types(t, self.s) else: return self.default(self.s) @@ -268,6 +270,12 @@ def visit_tuple_type(self, t: TupleType) -> ProperType: mypy.typeops.tuple_fallback(t)) assert isinstance(fallback, Instance) return TupleType(items, fallback) + elif isinstance(self.s, Instance) and self.s.type.fullname == 'builtins.tuple': + typ = self.s.args[0] + for item in t.items[1:]: + typ = self.join(item, typ) + + return Instance(self.s.type, [typ]) else: return self.default(self.s) diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index d482e90e2fa4..138ba1ccf84d 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -3126,3 +3126,30 @@ y = defaultdict(list) # E: Need type annotation for 'y' y['a'] = [] reveal_type(y) # N: Revealed type is 'collections.defaultdict[Any, Any]' [builtins fixtures/dict.pyi] + +[case testTupleAndInstanceJoin] +from typing import Tuple +x: Tuple[int, int] +y: Tuple[int, ...] + +lst = [x, y] +reveal_type(lst[0]) # N: Revealed type is 'builtins.tuple*[builtins.int]' +[builtins fixtures/list.pyi] + +[case testTupleAndInstanceJoinPartlySame] +from typing import Tuple +x: Tuple[int, str] +y: Tuple[int, ...] + +lst = [x, y] +reveal_type(lst[0]) # N: Revealed type is 'builtins.tuple*[builtins.object]' +[builtins fixtures/list.pyi] + +[case testTupleAndInstanceJoinAllDifferent] +from typing import Tuple +x: Tuple[str, float] +y: Tuple[int, ...] + +lst = [x, y] +reveal_type(lst[0]) # N: Revealed type is 'builtins.tuple*[builtins.object]' +[builtins fixtures/list.pyi] From 10e376bef706a99623d2b799447ff0f5240575e6 Mon Sep 17 00:00:00 2001 From: Theodore Liu Date: Mon, 6 Jan 2020 21:58:17 -0800 Subject: [PATCH 2/3] Update the join algorithm --- mypy/join.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/join.py b/mypy/join.py index f7f9abe2d0ed..3523692fbf2e 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -272,7 +272,7 @@ def visit_tuple_type(self, t: TupleType) -> ProperType: return TupleType(items, fallback) elif isinstance(self.s, Instance) and self.s.type.fullname == 'builtins.tuple': typ = self.s.args[0] - for item in t.items[1:]: + for item in t.items: typ = self.join(item, typ) return Instance(self.s.type, [typ]) From f3de77dc9821296e29fd8881f3306e13b43fddda Mon Sep 17 00:00:00 2001 From: Theodore Liu Date: Wed, 8 Jan 2020 18:09:38 -0800 Subject: [PATCH 3/3] Update join.py and add more tests --- mypy/join.py | 39 +++++++++------- mypy/test/testtypes.py | 2 +- test-data/unit/check-inference.test | 70 ++++++++++++++++++++++++++--- test-data/unit/fixtures/tuple.pyi | 1 + 4 files changed, 88 insertions(+), 24 deletions(-) diff --git a/mypy/join.py b/mypy/join.py index 3523692fbf2e..697619d35bff 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -262,22 +262,29 @@ def visit_overloaded(self, t: Overloaded) -> ProperType: return join_types(t.fallback, s) def visit_tuple_type(self, t: TupleType) -> ProperType: - if isinstance(self.s, TupleType) and self.s.length() == t.length(): - items = [] # type: List[Type] - for i in range(t.length()): - items.append(self.join(t.items[i], self.s.items[i])) - fallback = join_instances(mypy.typeops.tuple_fallback(self.s), - mypy.typeops.tuple_fallback(t)) - assert isinstance(fallback, Instance) - return TupleType(items, fallback) - elif isinstance(self.s, Instance) and self.s.type.fullname == 'builtins.tuple': - typ = self.s.args[0] - for item in t.items: - typ = self.join(item, typ) - - return Instance(self.s.type, [typ]) - else: - return self.default(self.s) + if isinstance(self.s, TupleType): + if self.s.length() == t.length(): + items = [] # type: List[Type] + for i in range(t.length()): + items.append(self.join(t.items[i], self.s.items[i])) + fallback = join_instances(mypy.typeops.tuple_fallback(self.s), + mypy.typeops.tuple_fallback(t)) + assert isinstance(fallback, Instance) + return TupleType(items, fallback) + else: + s_type = join_type_list(self.s.items) + t_type = join_type_list(t.items) + + return Instance(self.s.partial_fallback.type, [self.join(s_type, t_type)]) + + elif isinstance(self.s, Instance): + for typ in self.s.type.mro: + if typ.fullname == 'builtins.tuple': + # how to access the original args? i see we can recursively descend .bases + # but this seems cumbersome + return Instance(typ, [AnyType(4)]) # just a stub for now + + return self.default(self.s) def visit_typeddict_type(self, t: TypedDictType) -> ProperType: if isinstance(self.s, TypedDictType): diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index 4609e0dd1a02..4040f49cb768 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -501,7 +501,7 @@ def test_tuples(self) -> None: self.assert_join(self.tuple(self.fx.a, self.fx.a), self.fx.std_tuple, - self.fx.o) + self.fx.std_tuple) self.assert_join(self.tuple(self.fx.a), self.tuple(self.fx.a, self.fx.a), self.fx.o) diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 138ba1ccf84d..cf2be448430f 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -3127,7 +3127,7 @@ y['a'] = [] reveal_type(y) # N: Revealed type is 'collections.defaultdict[Any, Any]' [builtins fixtures/dict.pyi] -[case testTupleAndInstanceJoin] +[case testJoinTupleAndInstance] from typing import Tuple x: Tuple[int, int] y: Tuple[int, ...] @@ -3136,20 +3136,76 @@ lst = [x, y] reveal_type(lst[0]) # N: Revealed type is 'builtins.tuple*[builtins.int]' [builtins fixtures/list.pyi] -[case testTupleAndInstanceJoinPartlySame] +[case testJoinTupleAndInstancePartlySame] from typing import Tuple x: Tuple[int, str] y: Tuple[int, ...] lst = [x, y] reveal_type(lst[0]) # N: Revealed type is 'builtins.tuple*[builtins.object]' -[builtins fixtures/list.pyi] +[builtins fixtures/tuple.pyi] -[case testTupleAndInstanceJoinAllDifferent] +[case testJoinTupleAndInstanceAllDifferent] from typing import Tuple -x: Tuple[str, float] +x: Tuple[float, float] y: Tuple[int, ...] lst = [x, y] -reveal_type(lst[0]) # N: Revealed type is 'builtins.tuple*[builtins.object]' -[builtins fixtures/list.pyi] +reveal_type(lst[0]) # N: Revealed type is 'builtins.tuple*[builtins.float]' +[builtins fixtures/tuple.pyi] + +[case testJoinTupleAndEmptyTuple] +from typing import Tuple +x = () +y: Tuple[int] +z: bool + +res = x if z else y +reveal_type(res) # N: Revealed type is 'builtins.tuple[builtins.int]' + +res = y if z else x +reveal_type(res) # N: Revealed type is 'builtins.tuple[builtins.int]' + +[case testJoinTupleVariableAndEmptyTuple] +from typing import Tuple + +x = () +y: Tuple[int, ...] +z: bool + +res = x if z else y +reveal_type(res) # N: Revealed type is 'builtins.tuple[builtins.int]' + +[case testJoinTupleAndDifferentTupleLength] +from typing import Tuple +x: Tuple[int] +y: Tuple[int, int] +z: bool + +res = x if z else y +reveal_type(res) # N: Revealed type is 'builtins.tuple[builtins.int]' + +[case testJoinTupleAndDifferentTupleLengthType] +from typing import Tuple +x: Tuple[int, float] +y: Tuple[int] +z: bool + +res = x if z else y +reveal_type(res) # N: Revealed type is 'builtins.tuple[builtins.float]' + +[case testJoinTupleAndInstanceTupleSubclass] +from typing import Tuple + +class A(tuple): + pass + +class B(A): + pass + +x: B +y: Tuple[int] +z: bool + +res = x if z else y +reveal_type(res) # N: Revealed type is 'builtins.tuple[Any]' diff --git a/test-data/unit/fixtures/tuple.pyi b/test-data/unit/fixtures/tuple.pyi index 686e2dd55818..587993f05345 100644 --- a/test-data/unit/fixtures/tuple.pyi +++ b/test-data/unit/fixtures/tuple.pyi @@ -26,6 +26,7 @@ class int: class slice: pass class bool: pass class str: pass # For convenience +class float: pass # For convenience class unicode: pass T = TypeVar('T')