Skip to content

Commit 936bbe6

Browse files
committed
Fix Tuple behavior
1 parent 2e90e9f commit 936bbe6

15 files changed

+82
-59
lines changed

mypy/checker.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1511,7 +1511,7 @@ def append_types_for_inference(lvs: List[Expression], rv_types: List[Type]) -> N
15111511

15121512
append_types_for_inference(right_lvs, right_rv_types)
15131513

1514-
return TupleType(type_parameters, self.named_type('builtins.tuple'))
1514+
return TupleType(type_parameters, self.named_generic_type('builtins.tuple', [AnyType()]))
15151515

15161516
def split_around_star(self, items: List[T], star_index: int,
15171517
length: int) -> Tuple[List[T], List[T], List[T]]:
@@ -1574,7 +1574,7 @@ def check_lvalue(self, lvalue: Lvalue) -> Tuple[Type, IndexExpr, Var]:
15741574
self.store_type(lvalue, lvalue_type)
15751575
elif isinstance(lvalue, TupleExpr) or isinstance(lvalue, ListExpr):
15761576
types = [self.check_lvalue(sub_expr)[0] for sub_expr in lvalue.items]
1577-
lvalue_type = TupleType(types, self.named_type('builtins.tuple'))
1577+
lvalue_type = TupleType(types, self.named_generic_type('builtins.tuple', [AnyType()]))
15781578
else:
15791579
lvalue_type = self.expr_checker.accept(lvalue)
15801580

mypy/checkexpr.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1740,7 +1740,7 @@ def visit_dict_expr(self, e: DictExpr) -> Type:
17401740
#
17411741
# def <unnamed>(*v: Tuple[kt, vt]) -> Dict[kt, vt]: ...
17421742
constructor = CallableType(
1743-
[TupleType([kt, vt], self.named_type('builtins.tuple'))],
1743+
[TupleType([kt, vt], self.chk.named_generic_type('builtins.tuple', [AnyType()]))],
17441744
[nodes.ARG_STAR],
17451745
[None],
17461746
self.chk.named_generic_type('builtins.dict', [kt, vt]),

mypy/expandtype.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
FunctionLike, TypeVarDef
88
)
99

10+
import mypy
11+
1012

1113
def expand_type(typ: Type, env: Mapping[TypeVarId, Type]) -> Type:
1214
"""Substitute any type variable references in a type given by a type
@@ -109,7 +111,10 @@ def visit_overloaded(self, t: Overloaded) -> Type:
109111
return Overloaded(items)
110112

111113
def visit_tuple_type(self, t: TupleType) -> Type:
112-
return t.copy_modified(items=self.expand_types(t.items))
114+
new_items = self.expand_types(t.items)
115+
new_fallback_arg = mypy.join.join_type_list(new_items)
116+
new_fallback = t.fallback.copy_modified(args=[new_fallback_arg])
117+
return t.copy_modified(items=new_items, fallback=new_fallback)
113118

114119
def visit_typeddict_type(self, t: TypedDictType) -> Type:
115120
return t.copy_modified(item_types=self.expand_types(t.items.values()))

mypy/join.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,4 @@ def join_type_list(types: List[Type]) -> Type:
386386
# This is a little arbitrary but reasonable. Any empty tuple should be compatible
387387
# with all variable length tuples, and this makes it possible.
388388
return UninhabitedType()
389-
joined = types[0]
390-
for t in types[1:]:
391-
joined = join_types(joined, t)
392-
return joined
389+
return UnionType.make_simplified_union(types)

mypy/messages.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,10 @@ def format_simple(self, typ: Type, verbosity: int = 0) -> str:
260260
return '"{}"'.format(base_str)
261261
elif itype.type.fullname() == 'builtins.tuple':
262262
item_type_str = strip_quotes(self.format(itype.args[0]))
263-
return 'Tuple[{}, ...]'.format(item_type_str)
263+
if isinstance(itype.args[0], AnyType):
264+
return 'tuple'
265+
else:
266+
return 'Tuple[{}, ...]'.format(item_type_str)
264267
elif itype.type.fullname() in reverse_type_aliases:
265268
alias = reverse_type_aliases[itype.type.fullname()]
266269
alias = alias.split('.')[-1]

mypy/semanal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2107,7 +2107,7 @@ def build_namedtuple_typeinfo(self, name: str, items: List[str], types: List[Typ
21072107
# Actual signature should return OrderedDict[str, Union[types]]
21082108
ordereddictype = (self.named_type_or_none('builtins.dict', [strtype, AnyType()])
21092109
or self.object_type())
2110-
fallback = self.named_type('__builtins__.tuple', types)
2110+
fallback = self.named_type('__builtins__.tuple', [join.join_type_list(types)])
21112111
# Note: actual signature should accept an invariant version of Iterable[UnionType[types]].
21122112
# but it can't be expressed. 'new' and 'len' should be callable types.
21132113
iterable_type = self.named_type_or_none('typing.Iterable', [AnyType()])

mypy/subtypes.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -145,13 +145,7 @@ def visit_instance(self, left: Instance) -> bool:
145145
return False
146146
# Map left type to corresponding right instances.
147147
t = map_instance_to_supertype(left, right.type)
148-
# Check that one of the types does not have type args or
149-
# the number of type args is the same and
150-
# each left type arg is acceptable in place of the matching right one
151-
if not t.args or not right.args:
152-
return True
153-
if len(t.args) != len(right.args):
154-
return False
148+
# TODO: assert len(t.args) == len(right.args)
155149
return all(self.check_type_parameter(lefta, righta, tvar.variance)
156150
for lefta, righta, tvar in
157151
zip(t.args, right.args, right.type.defn.type_vars))

mypy/typeanal.py

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from mypy.exprtotype import expr_to_unanalyzed_type, TypeTranslationError
1919
from mypy.subtypes import is_subtype
2020
from mypy import nodes
21+
from mypy import join
2122
from mypy import experiments
2223

2324

@@ -137,7 +138,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type:
137138
elif fullname == 'typing.Tuple':
138139
if len(t.args) == 0 and not t.empty_tuple_index:
139140
# Bare 'Tuple' is same as 'tuple'
140-
return self.builtin_type('builtins.tuple')
141+
return self.builtin_type('builtins.tuple', [AnyType()])
141142
if len(t.args) == 2 and isinstance(t.args[1], EllipsisType):
142143
# Tuple[T, ...] (uniform, variable-length tuple)
143144
instance = self.builtin_type('builtins.tuple', [self.anal_type(t.args[0])])
@@ -211,37 +212,35 @@ def visit_unbound_type(self, t: UnboundType) -> Type:
211212
self.fail('Invalid type "{}"'.format(name), t)
212213
return t
213214
info = sym.node # type: TypeInfo
214-
if len(t.args) > 0 and info.fullname() == 'builtins.tuple':
215-
return TupleType(self.anal_array(t.args),
216-
Instance(info, [AnyType()], t.line),
217-
t.line)
218-
else:
219-
# Analyze arguments and construct Instance type. The
220-
# number of type arguments and their values are
221-
# checked only later, since we do not always know the
222-
# valid count at this point. Thus we may construct an
223-
# Instance with an invalid number of type arguments.
224-
instance = Instance(info, self.anal_array(t.args), t.line, t.column)
225-
tup = info.tuple_type
226-
if tup is not None:
227-
# The class has a Tuple[...] base class so it will be
228-
# represented as a tuple type.
229-
if t.args:
230-
self.fail('Generic tuple types not supported', t)
231-
return AnyType()
232-
return tup.copy_modified(items=self.anal_array(tup.items),
233-
fallback=instance)
234-
td = info.typeddict_type
235-
if td is not None:
236-
# The class has a TypedDict[...] base class so it will be
237-
# represented as a typeddict type.
238-
if t.args:
239-
self.fail('Generic TypedDict types not supported', t)
240-
return AnyType()
241-
# Create a named TypedDictType
242-
return td.copy_modified(item_types=self.anal_array(list(td.items.values())),
243-
fallback=instance)
244-
return instance
215+
if info.fullname() == 'builtins.tuple':
216+
assert not t.args
217+
t.args = [AnyType()]
218+
# Analyze arguments and construct Instance type. The
219+
# number of type arguments and their values are
220+
# checked only later, since we do not always know the
221+
# valid count at this point. Thus we may construct an
222+
# Instance with an invalid number of type arguments.
223+
instance = Instance(info, self.anal_array(t.args), t.line, t.column)
224+
tup = info.tuple_type
225+
if tup is not None:
226+
# The class has a Tuple[...] base class so it will be
227+
# represented as a tuple type.
228+
if t.args:
229+
self.fail('Generic tuple types not supported', t)
230+
return AnyType()
231+
return tup.copy_modified(items=self.anal_array(tup.items),
232+
fallback=instance)
233+
td = info.typeddict_type
234+
if td is not None:
235+
# The class has a TypedDict[...] base class so it will be
236+
# represented as a typeddict type.
237+
if t.args:
238+
self.fail('Generic TypedDict types not supported', t)
239+
return AnyType()
240+
# Create a named TypedDictType
241+
return td.copy_modified(item_types=self.anal_array(list(td.items.values())),
242+
fallback=instance)
243+
return instance
245244
else:
246245
return AnyType()
247246

@@ -332,7 +331,7 @@ def visit_tuple_type(self, t: TupleType) -> Type:
332331
self.fail('At most one star type allowed in a tuple', t)
333332
if t.implicit:
334333
return TupleType([AnyType() for _ in t.items],
335-
self.builtin_type('builtins.tuple'),
334+
self.builtin_type('builtins.tuple', [AnyType()]),
336335
t.line)
337336
else:
338337
return AnyType()
@@ -520,6 +519,11 @@ def visit_callable_type(self, t: CallableType) -> None:
520519
def visit_tuple_type(self, t: TupleType) -> None:
521520
for item in t.items:
522521
item.accept(self)
522+
# if it's not builtins.tuple, then its bases should have tuple[Any]
523+
# TODO: put assert here if it's not too slow
524+
if type(t.fallback) == Instance and t.fallback.type.fullname() == 'builtins.tuple':
525+
fallback_item = join.join_type_list(t.items)
526+
t.fallback.args = [fallback_item]
523527

524528
def visit_typeddict_type(self, t: TypedDictType) -> None:
525529
for item_type in t.items.values():

mypy/types.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
)
1616

1717
from mypy.sharedparse import argument_elide_name
18+
import mypy
19+
20+
MYPY = False
21+
if MYPY:
22+
from mypy import join
1823

1924

2025
T = TypeVar('T')
@@ -378,6 +383,7 @@ class Instance(Type):
378383
def __init__(self, typ: Optional[mypy.nodes.TypeInfo], args: List[Type],
379384
line: int = -1, column: int = -1, erased: bool = False) -> None:
380385
assert(typ is None or typ.fullname() not in ["builtins.Any", "typing.Any"])
386+
# TODO: assert(typ is None or typ.fullname() != 'builtins.tuple' or len(args) == 1)
381387
self.type = typ
382388
self.args = args
383389
self.erased = erased
@@ -833,6 +839,8 @@ def __init__(self, items: List[Type], fallback: Instance, line: int = -1,
833839
column: int = -1, implicit: bool = False) -> None:
834840
self.items = items
835841
self.fallback = fallback
842+
# TODO: assert not (isinstance(fallback, Instance) and fallback.type and
843+
# fallback.type.fullname() == 'builtins.tuple' and not fallback.args)
836844
self.implicit = implicit
837845
self.can_be_true = len(self.items) > 0
838846
self.can_be_false = len(self.items) == 0
@@ -867,7 +875,10 @@ def copy_modified(self, *, fallback: Instance = None,
867875
return TupleType(items, fallback, self.line, self.column)
868876

869877
def slice(self, begin: int, stride: int, end: int) -> 'TupleType':
870-
return TupleType(self.items[begin:end:stride], self.fallback,
878+
new_items = self.items[begin:end:stride]
879+
fallback_args = [mypy.join.join_type_list(new_items)]
880+
new_fallback = self.fallback.copy_modified(args=fallback_args)
881+
return TupleType(new_items, new_fallback,
871882
self.line, self.column, self.implicit)
872883

873884

@@ -1707,7 +1718,9 @@ def set_typ_args(tp: Type, new_args: List[Type], line: int = -1, column: int = -
17071718
if isinstance(tp, Instance):
17081719
return Instance(tp.type, new_args, line, column)
17091720
if isinstance(tp, TupleType):
1710-
return tp.copy_modified(items=new_args)
1721+
fallback_args = [mypy.join.join_type_list(new_args)]
1722+
fallback = tp.fallback.copy_modified(args=fallback_args)
1723+
return tp.copy_modified(items=new_args, fallback=fallback)
17111724
if isinstance(tp, UnionType):
17121725
return UnionType.make_simplified_union(new_args, line, column)
17131726
if isinstance(tp, CallableType):

test-data/unit/check-functions.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1486,7 +1486,7 @@ f(1, thing_in_kwargs=["hey"])
14861486
from typing import Tuple, Any
14871487
def f(x, *args): # type: (...) -> None
14881488
success_tuple_type = args # type: Tuple[Any, ...]
1489-
fail_tuple_type = args # type: None # E: Incompatible types in assignment (expression has type Tuple[Any, ...], variable has type None)
1489+
fail_tuple_type = args # type: None # E: Incompatible types in assignment (expression has type tuple, variable has type None)
14901490
f(1, "hello")
14911491
[builtins fixtures/tuple.pyi]
14921492
[out]

test-data/unit/check-inference.test

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1775,6 +1775,8 @@ def g(x: Union[int, str]): pass
17751775
c = a if f() else b
17761776
g(c) # E: Argument 1 to "g" has incompatible type "Union[int, str, tuple]"; expected "Union[int, str]"
17771777

1778+
[builtins fixtures/list.pyi]
1779+
17781780
[case testUnificationMultipleInheritance]
17791781
class A: pass
17801782
class B:

test-data/unit/check-tuples.test

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ from typing import Tuple
9191
t1 = None # type: Tuple[A, A]
9292
t2 = None # type: tuple
9393

94-
t1 = t2 # E: Incompatible types in assignment (expression has type Tuple[Any, ...], variable has type "Tuple[A, A]")
94+
t1 = t2 # E: Incompatible types in assignment (expression has type tuple, variable has type "Tuple[A, A]")
9595
t2 = t1
9696

9797
class A: pass
@@ -702,6 +702,7 @@ B()[100]
702702
[case testValidTupleBaseClass]
703703
from typing import Tuple
704704
class A(tuple): pass
705+
[builtins fixtures/tuple.pyi]
705706
[out]
706707

707708
[case testTupleBaseClass2-skip]
@@ -913,7 +914,7 @@ def f(a: Tuple) -> None: pass
913914
f(())
914915
f((1,))
915916
f(('', ''))
916-
f(0) # E: Argument 1 to "f" has incompatible type "int"; expected Tuple[Any, ...]
917+
f(0) # E: Argument 1 to "f" has incompatible type "int"; expected tuple
917918
[builtins fixtures/tuple.pyi]
918919

919920
[case testTupleSingleton]

test-data/unit/check-typeddict.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ Point3D = TypedDict('Point3D', {'x': int, 'y': int, 'z': int})
367367
p1 = TaggedPoint(type='2d', x=0, y=0)
368368
p2 = Point3D(x=1, y=1, z=1)
369369
joined_points = [p1, p2]
370-
reveal_type(p1) # E: Revealed type is 'TypedDict(type=builtins.str, x=builtins.int, y=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.object])'
370+
reveal_type(p1) # E: Revealed type is 'TypedDict(type=builtins.str, x=builtins.int, y=builtins.int, _fallback=typing.Mapping[builtins.str, Union[builtins.str, builtins.int]])'
371371
reveal_type(p2) # E: Revealed type is 'TypedDict(x=builtins.int, y=builtins.int, z=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])'
372372
reveal_type(joined_points) # E: Revealed type is 'builtins.list[TypedDict(x=builtins.int, y=builtins.int, _fallback=typing.Mapping[builtins.str, builtins.int])]'
373373
[builtins fixtures/dict.pyi]

test-data/unit/fixtures/isinstancelist.pyi

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import builtinclass, Iterable, Iterator, TypeVar, List, Mapping, overload, Tuple, Set, Union
1+
from typing import builtinclass, Iterable, Iterator, TypeVar, List, Mapping, overload, Tuple, Set, Union, Sequence, Generic
22

33
@builtinclass
44
class object:
@@ -8,7 +8,11 @@ class object:
88
class type:
99
def __init__(self, x) -> None: pass
1010

11-
class tuple: pass
11+
Tco = TypeVar('Tco', covariant=True)
12+
class tuple(Sequence[Tco], Generic[Tco]):
13+
def __iter__(self) -> Iterator[Tco]: pass
14+
def __getitem__(self, x: int) -> Tco: pass
15+
1216
class function: pass
1317

1418
def isinstance(x: object, t: Union[type, Tuple]) -> bool: pass

0 commit comments

Comments
 (0)