Skip to content

Commit 2bc14eb

Browse files
authored
Fixes to tuple fallbacks (#6442)
This changes tuple fallbacks to be calculated on demand during type checking. This fixes some issues with fallbacks being imprecise. In the new semantic analyzer, this introduces a new pass just after the main semantic analysis pass to calculate precise item types for `tuple` base classes. These can't be calculated on demand since the base class is a variable-length tuple type (`Instance`) instead of `TupleType`. We can't calculate these during the main semantic analysis pass since base classes can be incomplete. Fixes #6400.
1 parent bd6af1c commit 2bc14eb

32 files changed

+299
-136
lines changed

mypy/applytype.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from typing import Dict, Sequence, Optional
22

33
import mypy.subtypes
4-
from mypy.sametypes import is_same_type
4+
import mypy.sametypes
55
from mypy.expandtype import expand_type
66
from mypy.types import Type, TypeVarId, TypeVarType, CallableType, AnyType, PartialType
77
from mypy.messages import MessageBuilder
@@ -37,7 +37,7 @@ def apply_generic_arguments(callable: CallableType, orig_types: Sequence[Optiona
3737
if isinstance(type, TypeVarType) and type.values:
3838
# Allow substituting T1 for T if every allowed value of T1
3939
# is also a legal value of T.
40-
if all(any(is_same_type(v, v1) for v in values)
40+
if all(any(mypy.sametypes.is_same_type(v, v1) for v in values)
4141
for v1 in type.values):
4242
continue
4343
matching = []

mypy/checker.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
from mypy.plugin import Plugin, CheckerPluginInterface
6464
from mypy.sharedparse import BINARY_MAGIC_METHODS
6565
from mypy.scope import Scope
66+
from mypy.typeops import tuple_fallback
6667
from mypy import state
6768

6869
MYPY = False
@@ -1063,7 +1064,9 @@ def check_reverse_op_method(self, defn: FuncItem,
10631064
forward_inst = reverse_type.arg_types[1]
10641065
if isinstance(forward_inst, TypeVarType):
10651066
forward_inst = forward_inst.upper_bound
1066-
if isinstance(forward_inst, (FunctionLike, TupleType, TypedDictType, LiteralType)):
1067+
elif isinstance(forward_inst, TupleType):
1068+
forward_inst = tuple_fallback(forward_inst)
1069+
elif isinstance(forward_inst, (FunctionLike, TypedDictType, LiteralType)):
10671070
forward_inst = forward_inst.fallback
10681071
if isinstance(forward_inst, TypeType):
10691072
item = forward_inst.item
@@ -1955,7 +1958,7 @@ def lvalue_type_from_base(self, expr_node: Var,
19551958
self_type = self.scope.active_self_type()
19561959
assert self_type is not None, "Internal error: base lookup outside class"
19571960
if isinstance(self_type, TupleType):
1958-
instance = self_type.fallback
1961+
instance = tuple_fallback(self_type)
19591962
else:
19601963
instance = self_type
19611964
itype = map_instance_to_supertype(instance, base)
@@ -3260,7 +3263,7 @@ def partition_by_callable(self, typ: Type,
32603263
# when we dummy up a new type.
32613264
ityp = typ
32623265
if isinstance(typ, TupleType):
3263-
ityp = typ.fallback
3266+
ityp = tuple_fallback(typ)
32643267

32653268
if isinstance(ityp, Instance):
32663269
method = ityp.type.get_method('__call__')

mypy/checkexpr.py

+18-12
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
from mypy.visitor import ExpressionVisitor
6060
from mypy.plugin import Plugin, MethodContext, MethodSigContext, FunctionContext
6161
from mypy.typeanal import make_optional_type
62+
from mypy.typeops import tuple_fallback
6263

6364
# Type of callback user for checking individual function arguments. See
6465
# check_args() below for details.
@@ -347,7 +348,7 @@ def method_fullname(self, object_type: Type, method_name: str) -> Optional[str]:
347348
info = object_type.fallback.type.get_containing_type_info(method_name)
348349
type_name = info.fullname() if info is not None else None
349350
elif isinstance(object_type, TupleType):
350-
type_name = object_type.fallback.type.fullname()
351+
type_name = tuple_fallback(object_type).type.fullname()
351352

352353
if type_name is not None:
353354
return '{}.{}'.format(type_name, method_name)
@@ -722,7 +723,7 @@ def check_call(self,
722723
return self.check_call(item, args, arg_kinds, context, arg_names,
723724
callable_node, arg_messages)
724725
elif isinstance(callee, TupleType):
725-
return self.check_call(callee.fallback, args, arg_kinds, context,
726+
return self.check_call(tuple_fallback(callee), args, arg_kinds, context,
726727
arg_names, callable_node, arg_messages, callable_name,
727728
object_type)
728729
else:
@@ -835,8 +836,9 @@ def analyze_type_type_callee(self, item: Type, context: Context) -> Type:
835836
if callee:
836837
return callee
837838
# We support Type of namedtuples but not of tuples in general
838-
if isinstance(item, TupleType) and item.fallback.type.fullname() != 'builtins.tuple':
839-
return self.analyze_type_type_callee(item.fallback, context)
839+
if (isinstance(item, TupleType)
840+
and tuple_fallback(item).type.fullname() != 'builtins.tuple'):
841+
return self.analyze_type_type_callee(tuple_fallback(item), context)
840842

841843
self.msg.unsupported_type_type(item, context)
842844
return AnyType(TypeOfAny.from_error)
@@ -2666,8 +2668,8 @@ class LongName(Generic[T]): ...
26662668
return self.apply_type_arguments_to_callable(tp, item.args, ctx)
26672669
elif (isinstance(item, TupleType) and
26682670
# Tuple[str, int]() fails at runtime, only named tuples and subclasses work.
2669-
item.fallback.type.fullname() != 'builtins.tuple'):
2670-
return type_object_type(item.fallback.type, self.named_type)
2671+
tuple_fallback(item).type.fullname() != 'builtins.tuple'):
2672+
return type_object_type(tuple_fallback(item).type, self.named_type)
26712673
elif isinstance(item, AnyType):
26722674
return AnyType(TypeOfAny.from_another_any, source_any=item)
26732675
else:
@@ -2785,7 +2787,8 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type:
27852787
tt = self.accept(item, type_context_items[j])
27862788
j += 1
27872789
items.append(tt)
2788-
fallback_item = join.join_type_list(items)
2790+
# This is a partial fallback item type. A precise type will be calculated on demand.
2791+
fallback_item = AnyType(TypeOfAny.special_form)
27892792
return TupleType(items, self.chk.named_generic_type('builtins.tuple', [fallback_item]))
27902793

27912794
def visit_dict_expr(self, e: DictExpr) -> Type:
@@ -2973,7 +2976,8 @@ def check_super_arguments(self, e: SuperExpr) -> None:
29732976
# Could be anything.
29742977
return
29752978
if isinstance(item, TupleType):
2976-
item = item.fallback # Handle named tuples and other Tuple[...] subclasses.
2979+
# Handle named tuples and other Tuple[...] subclasses.
2980+
item = tuple_fallback(item)
29772981
if not isinstance(item, Instance):
29782982
# A complicated type object type. Too tricky, give up.
29792983
# TODO: Do something more clever here.
@@ -2997,7 +3001,7 @@ def check_super_arguments(self, e: SuperExpr) -> None:
29973001
return
29983002
if isinstance(instance_type, TupleType):
29993003
# Needed for named tuples and other Tuple[...] subclasses.
3000-
instance_type = instance_type.fallback
3004+
instance_type = tuple_fallback(instance_type)
30013005
if type_info not in instance_type.type.mro:
30023006
self.chk.fail(message_registry.SUPER_ARG_2_NOT_INSTANCE_OF_ARG_1, e)
30033007
elif isinstance(instance_type, TypeType) or (isinstance(instance_type, FunctionLike)
@@ -3287,7 +3291,9 @@ def has_member(self, typ: Type, member: str) -> bool:
32873291
# these two should be carefully kept in sync.
32883292
if isinstance(typ, TypeVarType):
32893293
typ = typ.upper_bound
3290-
if isinstance(typ, (TupleType, LiteralType)):
3294+
if isinstance(typ, TupleType):
3295+
typ = tuple_fallback(typ)
3296+
if isinstance(typ, LiteralType):
32913297
typ = typ.fallback
32923298
if isinstance(typ, Instance):
32933299
return typ.type.has_readable_member(member)
@@ -3305,7 +3311,7 @@ def has_member(self, typ: Type, member: str) -> bool:
33053311
if isinstance(item, TypeVarType):
33063312
item = item.upper_bound
33073313
if isinstance(item, TupleType):
3308-
item = item.fallback
3314+
item = tuple_fallback(item)
33093315
if isinstance(item, Instance) and item.type.metaclass_type is not None:
33103316
return self.has_member(item.type.metaclass_type, member)
33113317
if isinstance(item, AnyType):
@@ -3645,7 +3651,7 @@ def is_typetype_like(typ: Type) -> bool:
36453651
if isinstance(actual, Overloaded):
36463652
actual = actual.items()[0].fallback
36473653
if isinstance(actual, TupleType):
3648-
actual = actual.fallback
3654+
actual = tuple_fallback(actual)
36493655
if isinstance(actual, Instance) and formal.type in actual.type.mro:
36503656
# Try performing a quick check as an optimization
36513657
return True

mypy/checkmember.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from mypy import message_registry
2323
from mypy import subtypes
2424
from mypy import meet
25+
from mypy.typeops import tuple_fallback
2526

2627
MYPY = False
2728
if MYPY: # import for forward declaration only
@@ -122,7 +123,10 @@ def _analyze_member_access(name: str,
122123
return analyze_type_callable_member_access(name, typ, mx)
123124
elif isinstance(typ, TypeType):
124125
return analyze_type_type_member_access(name, typ, mx)
125-
elif isinstance(typ, (TupleType, TypedDictType, LiteralType, FunctionLike)):
126+
elif isinstance(typ, TupleType):
127+
# Actually look up from the fallback instance type.
128+
return _analyze_member_access(name, tuple_fallback(typ), mx)
129+
elif isinstance(typ, (TypedDictType, LiteralType, FunctionLike)):
126130
# Actually look up from the fallback instance type.
127131
return _analyze_member_access(name, typ.fallback, mx)
128132
elif isinstance(typ, NoneTyp):
@@ -195,7 +199,7 @@ def analyze_type_callable_member_access(name: str,
195199
# TODO super?
196200
ret_type = typ.items()[0].ret_type
197201
if isinstance(ret_type, TupleType):
198-
ret_type = ret_type.fallback
202+
ret_type = tuple_fallback(ret_type)
199203
if isinstance(ret_type, Instance):
200204
if not mx.is_operator:
201205
# When Python sees an operator (eg `3 == 4`), it automatically translates that
@@ -235,7 +239,7 @@ def analyze_type_type_member_access(name: str, typ: TypeType, mx: MemberContext)
235239
if isinstance(typ.item.upper_bound, Instance):
236240
item = typ.item.upper_bound
237241
elif isinstance(typ.item, TupleType):
238-
item = typ.item.fallback
242+
item = tuple_fallback(typ.item)
239243
elif isinstance(typ.item, FunctionLike) and typ.item.is_type_obj():
240244
item = typ.item.fallback
241245
elif isinstance(typ.item, TypeType):
@@ -804,7 +808,7 @@ def map_type_from_supertype(typ: Type,
804808
# Create the type of self in subtype, of form t[a1, ...].
805809
inst_type = fill_typevars(sub_info)
806810
if isinstance(inst_type, TupleType):
807-
inst_type = inst_type.fallback
811+
inst_type = tuple_fallback(inst_type)
808812
# Map the type of self to supertype. This gets us a description of the
809813
# supertype type variables in terms of subtype variables, i.e. t[t1, ...]
810814
# so that any type variables in tN are to be interpreted in subtype

mypy/constraints.py

+12-6
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
)
1010
from mypy.maptype import map_instance_to_supertype
1111
import mypy.subtypes
12-
from mypy.sametypes import is_same_type
12+
import mypy.sametypes
13+
import mypy.typeops
1314
from mypy.erasetype import erase_typevars
1415
from mypy.nodes import COVARIANT, CONTRAVARIANT
1516
from mypy.argmap import ArgTypeExpander
@@ -199,7 +200,7 @@ def is_same_constraints(x: List[Constraint], y: List[Constraint]) -> bool:
199200
def is_same_constraint(c1: Constraint, c2: Constraint) -> bool:
200201
return (c1.type_var == c2.type_var
201202
and c1.op == c2.op
202-
and is_same_type(c1.target, c2.target))
203+
and mypy.sametypes.is_same_type(c1.target, c2.target))
203204

204205

205206
def simplify_away_incomplete_types(types: List[Type]) -> List[Type]:
@@ -282,7 +283,8 @@ def visit_instance(self, template: Instance) -> List[Constraint]:
282283
if isinstance(actual, (CallableType, Overloaded)) and template.type.is_protocol:
283284
if template.type.protocol_members == ['__call__']:
284285
# Special case: a generic callback protocol
285-
if not any(is_same_type(template, t) for t in template.type.inferring):
286+
if not any(mypy.sametypes.is_same_type(template, t)
287+
for t in template.type.inferring):
286288
template.type.inferring.append(template)
287289
call = mypy.subtypes.find_member('__call__', template, actual)
288290
assert call is not None
@@ -340,7 +342,8 @@ def visit_instance(self, template: Instance) -> List[Constraint]:
340342
# Note that we use is_protocol_implementation instead of is_subtype
341343
# because some type may be considered a subtype of a protocol
342344
# due to _promote, but still not implement the protocol.
343-
not any(is_same_type(template, t) for t in template.type.inferring) and
345+
not any(mypy.sametypes.is_same_type(template, t)
346+
for t in template.type.inferring) and
344347
mypy.subtypes.is_protocol_implementation(instance, erased)):
345348
template.type.inferring.append(template)
346349
self.infer_constraints_from_protocol_members(res, instance, template,
@@ -349,7 +352,8 @@ def visit_instance(self, template: Instance) -> List[Constraint]:
349352
return res
350353
elif (instance.type.is_protocol and self.direction == SUBTYPE_OF and
351354
# We avoid infinite recursion for structural subtypes also here.
352-
not any(is_same_type(instance, i) for i in instance.type.inferring) and
355+
not any(mypy.sametypes.is_same_type(instance, i)
356+
for i in instance.type.inferring) and
353357
mypy.subtypes.is_protocol_implementation(erased, instance)):
354358
instance.type.inferring.append(instance)
355359
self.infer_constraints_from_protocol_members(res, instance, template,
@@ -370,7 +374,9 @@ def visit_instance(self, template: Instance) -> List[Constraint]:
370374
res.extend(cb)
371375
return res
372376
elif isinstance(actual, TupleType) and self.direction == SUPERTYPE_OF:
373-
return infer_constraints(template, actual.fallback, self.direction)
377+
return infer_constraints(template,
378+
mypy.typeops.tuple_fallback(actual),
379+
self.direction)
374380
else:
375381
return []
376382

mypy/erasetype.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def visit_overloaded(self, t: Overloaded) -> Type:
7373
return t.fallback.accept(self)
7474

7575
def visit_tuple_type(self, t: TupleType) -> Type:
76-
return t.fallback.accept(self)
76+
return t.partial_fallback.accept(self)
7777

7878
def visit_typeddict_type(self, t: TypedDictType) -> Type:
7979
return t.fallback.accept(self)

mypy/fixup.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,8 @@ def visit_tuple_type(self, tt: TupleType) -> None:
199199
if tt.items:
200200
for it in tt.items:
201201
it.accept(self)
202-
if tt.fallback is not None:
203-
tt.fallback.accept(self)
202+
if tt.partial_fallback is not None:
203+
tt.partial_fallback.accept(self)
204204

205205
def visit_typeddict_type(self, tdt: TypedDictType) -> None:
206206
if tdt.items:

mypy/indirection.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def visit_overloaded(self, t: types.Overloaded) -> Set[str]:
8585
return self._visit(t.items()) | self._visit(t.fallback)
8686

8787
def visit_tuple_type(self, t: types.TupleType) -> Set[str]:
88-
return self._visit(t.items) | self._visit(t.fallback)
88+
return self._visit(t.items) | self._visit(t.partial_fallback)
8989

9090
def visit_typeddict_type(self, t: types.TypedDictType) -> Set[str]:
9191
return self._visit(t.items.values()) | self._visit(t.fallback)

mypy/join.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
is_protocol_implementation, find_member
1515
)
1616
from mypy.nodes import ARG_NAMED, ARG_NAMED_OPT
17+
import mypy.typeops
1718
from mypy import state
1819

1920

@@ -243,7 +244,8 @@ def visit_tuple_type(self, t: TupleType) -> Type:
243244
items = [] # type: List[Type]
244245
for i in range(t.length()):
245246
items.append(self.join(t.items[i], self.s.items[i]))
246-
fallback = join_instances(self.s.fallback, t.fallback)
247+
fallback = join_instances(mypy.typeops.tuple_fallback(self.s),
248+
mypy.typeops.tuple_fallback(t))
247249
assert isinstance(fallback, Instance)
248250
return TupleType(items, fallback)
249251
else:
@@ -299,7 +301,7 @@ def default(self, typ: Type) -> Type:
299301
elif isinstance(typ, UnboundType):
300302
return AnyType(TypeOfAny.special_form)
301303
elif isinstance(typ, TupleType):
302-
return self.default(typ.fallback)
304+
return self.default(mypy.typeops.tuple_fallback(typ))
303305
elif isinstance(typ, TypedDictType):
304306
return self.default(typ.fallback)
305307
elif isinstance(typ, FunctionLike):

mypy/meet.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
)
1616
from mypy.erasetype import erase_type
1717
from mypy.maptype import map_instance_to_supertype
18+
from mypy.typeops import tuple_fallback
1819
from mypy import state
1920

2021
# TODO Describe this module.
@@ -211,9 +212,9 @@ def is_none_typevar_overlap(t1: Type, t2: Type) -> bool:
211212
if is_tuple(left) and is_tuple(right):
212213
return are_tuples_overlapping(left, right, ignore_promotions=ignore_promotions)
213214
elif isinstance(left, TupleType):
214-
left = left.fallback
215+
left = tuple_fallback(left)
215216
elif isinstance(right, TupleType):
216-
right = right.fallback
217+
right = tuple_fallback(right)
217218

218219
# Next, we handle single-variant types that cannot be inherently partially overlapping,
219220
# but do require custom logic to inspect.
@@ -515,7 +516,7 @@ def visit_tuple_type(self, t: TupleType) -> Type:
515516
for i in range(t.length()):
516517
items.append(self.meet(t.items[i], self.s.items[i]))
517518
# TODO: What if the fallbacks are different?
518-
return TupleType(items, t.fallback)
519+
return TupleType(items, tuple_fallback(t))
519520
elif isinstance(self.s, Instance):
520521
# meet(Tuple[t1, t2, <...>], Tuple[s, ...]) == Tuple[meet(t1, s), meet(t2, s), <...>].
521522
if self.s.type.fullname() == 'builtins.tuple' and self.s.args:

mypy/messages.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,8 @@ def format_bare(self, typ: Type, verbosity: int = 0) -> str:
208208
return typ.name
209209
elif isinstance(typ, TupleType):
210210
# Prefer the name of the fallback class (if not tuple), as it's more informative.
211-
if typ.fallback.type.fullname() != 'builtins.tuple':
212-
return self.format_bare(typ.fallback)
211+
if typ.partial_fallback.type.fullname() != 'builtins.tuple':
212+
return self.format_bare(typ.partial_fallback)
213213
items = []
214214
for t in typ.items:
215215
items.append(self.format_bare(t))
@@ -1225,7 +1225,11 @@ def report_protocol_problems(self, subtype: Union[Instance, TupleType, TypedDict
12251225
# This will be only confusing a user even more.
12261226
return
12271227

1228-
if isinstance(subtype, (TupleType, TypedDictType)):
1228+
if isinstance(subtype, TupleType):
1229+
if not isinstance(subtype.partial_fallback, Instance):
1230+
return
1231+
subtype = subtype.partial_fallback
1232+
elif isinstance(subtype, TypedDictType):
12291233
if not isinstance(subtype.fallback, Instance):
12301234
return
12311235
subtype = subtype.fallback

0 commit comments

Comments
 (0)