diff --git a/mypy/applytype.py b/mypy/applytype.py index afd963928eee..b72253385c36 100644 --- a/mypy/applytype.py +++ b/mypy/applytype.py @@ -1,7 +1,7 @@ from typing import Dict, Sequence, Optional import mypy.subtypes -from mypy.sametypes import is_same_type +import mypy.sametypes from mypy.expandtype import expand_type from mypy.types import Type, TypeVarId, TypeVarType, CallableType, AnyType, PartialType from mypy.messages import MessageBuilder @@ -37,7 +37,7 @@ def apply_generic_arguments(callable: CallableType, orig_types: Sequence[Optiona if isinstance(type, TypeVarType) and type.values: # Allow substituting T1 for T if every allowed value of T1 # is also a legal value of T. - if all(any(is_same_type(v, v1) for v in values) + if all(any(mypy.sametypes.is_same_type(v, v1) for v in values) for v1 in type.values): continue matching = [] diff --git a/mypy/checker.py b/mypy/checker.py index f3dc5d75f06d..7cac7b9cfb1f 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -63,6 +63,7 @@ from mypy.plugin import Plugin, CheckerPluginInterface from mypy.sharedparse import BINARY_MAGIC_METHODS from mypy.scope import Scope +from mypy.typeops import tuple_fallback from mypy import state MYPY = False @@ -1063,7 +1064,9 @@ def check_reverse_op_method(self, defn: FuncItem, forward_inst = reverse_type.arg_types[1] if isinstance(forward_inst, TypeVarType): forward_inst = forward_inst.upper_bound - if isinstance(forward_inst, (FunctionLike, TupleType, TypedDictType, LiteralType)): + elif isinstance(forward_inst, TupleType): + forward_inst = tuple_fallback(forward_inst) + elif isinstance(forward_inst, (FunctionLike, TypedDictType, LiteralType)): forward_inst = forward_inst.fallback if isinstance(forward_inst, TypeType): item = forward_inst.item @@ -1955,7 +1958,7 @@ def lvalue_type_from_base(self, expr_node: Var, self_type = self.scope.active_self_type() assert self_type is not None, "Internal error: base lookup outside class" if isinstance(self_type, TupleType): - instance = self_type.fallback + instance = tuple_fallback(self_type) else: instance = self_type itype = map_instance_to_supertype(instance, base) @@ -3260,7 +3263,7 @@ def partition_by_callable(self, typ: Type, # when we dummy up a new type. ityp = typ if isinstance(typ, TupleType): - ityp = typ.fallback + ityp = tuple_fallback(typ) if isinstance(ityp, Instance): method = ityp.type.get_method('__call__') diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 60aa0bd75e69..a0ccfbe0c56a 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -59,6 +59,7 @@ from mypy.visitor import ExpressionVisitor from mypy.plugin import Plugin, MethodContext, MethodSigContext, FunctionContext from mypy.typeanal import make_optional_type +from mypy.typeops import tuple_fallback # Type of callback user for checking individual function arguments. See # check_args() below for details. @@ -347,7 +348,7 @@ def method_fullname(self, object_type: Type, method_name: str) -> Optional[str]: info = object_type.fallback.type.get_containing_type_info(method_name) type_name = info.fullname() if info is not None else None elif isinstance(object_type, TupleType): - type_name = object_type.fallback.type.fullname() + type_name = tuple_fallback(object_type).type.fullname() if type_name is not None: return '{}.{}'.format(type_name, method_name) @@ -722,7 +723,7 @@ def check_call(self, return self.check_call(item, args, arg_kinds, context, arg_names, callable_node, arg_messages) elif isinstance(callee, TupleType): - return self.check_call(callee.fallback, args, arg_kinds, context, + return self.check_call(tuple_fallback(callee), args, arg_kinds, context, arg_names, callable_node, arg_messages, callable_name, object_type) else: @@ -835,8 +836,9 @@ def analyze_type_type_callee(self, item: Type, context: Context) -> Type: if callee: return callee # We support Type of namedtuples but not of tuples in general - if isinstance(item, TupleType) and item.fallback.type.fullname() != 'builtins.tuple': - return self.analyze_type_type_callee(item.fallback, context) + if (isinstance(item, TupleType) + and tuple_fallback(item).type.fullname() != 'builtins.tuple'): + return self.analyze_type_type_callee(tuple_fallback(item), context) self.msg.unsupported_type_type(item, context) return AnyType(TypeOfAny.from_error) @@ -2666,8 +2668,8 @@ class LongName(Generic[T]): ... return self.apply_type_arguments_to_callable(tp, item.args, ctx) elif (isinstance(item, TupleType) and # Tuple[str, int]() fails at runtime, only named tuples and subclasses work. - item.fallback.type.fullname() != 'builtins.tuple'): - return type_object_type(item.fallback.type, self.named_type) + tuple_fallback(item).type.fullname() != 'builtins.tuple'): + return type_object_type(tuple_fallback(item).type, self.named_type) elif isinstance(item, AnyType): return AnyType(TypeOfAny.from_another_any, source_any=item) else: @@ -2785,7 +2787,8 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type: tt = self.accept(item, type_context_items[j]) j += 1 items.append(tt) - fallback_item = join.join_type_list(items) + # This is a partial fallback item type. A precise type will be calculated on demand. + fallback_item = AnyType(TypeOfAny.special_form) return TupleType(items, self.chk.named_generic_type('builtins.tuple', [fallback_item])) def visit_dict_expr(self, e: DictExpr) -> Type: @@ -2973,7 +2976,8 @@ def check_super_arguments(self, e: SuperExpr) -> None: # Could be anything. return if isinstance(item, TupleType): - item = item.fallback # Handle named tuples and other Tuple[...] subclasses. + # Handle named tuples and other Tuple[...] subclasses. + item = tuple_fallback(item) if not isinstance(item, Instance): # A complicated type object type. Too tricky, give up. # TODO: Do something more clever here. @@ -2997,7 +3001,7 @@ def check_super_arguments(self, e: SuperExpr) -> None: return if isinstance(instance_type, TupleType): # Needed for named tuples and other Tuple[...] subclasses. - instance_type = instance_type.fallback + instance_type = tuple_fallback(instance_type) if type_info not in instance_type.type.mro: self.chk.fail(message_registry.SUPER_ARG_2_NOT_INSTANCE_OF_ARG_1, e) elif isinstance(instance_type, TypeType) or (isinstance(instance_type, FunctionLike) @@ -3287,7 +3291,9 @@ def has_member(self, typ: Type, member: str) -> bool: # these two should be carefully kept in sync. if isinstance(typ, TypeVarType): typ = typ.upper_bound - if isinstance(typ, (TupleType, LiteralType)): + if isinstance(typ, TupleType): + typ = tuple_fallback(typ) + if isinstance(typ, LiteralType): typ = typ.fallback if isinstance(typ, Instance): return typ.type.has_readable_member(member) @@ -3305,7 +3311,7 @@ def has_member(self, typ: Type, member: str) -> bool: if isinstance(item, TypeVarType): item = item.upper_bound if isinstance(item, TupleType): - item = item.fallback + item = tuple_fallback(item) if isinstance(item, Instance) and item.type.metaclass_type is not None: return self.has_member(item.type.metaclass_type, member) if isinstance(item, AnyType): @@ -3645,7 +3651,7 @@ def is_typetype_like(typ: Type) -> bool: if isinstance(actual, Overloaded): actual = actual.items()[0].fallback if isinstance(actual, TupleType): - actual = actual.fallback + actual = tuple_fallback(actual) if isinstance(actual, Instance) and formal.type in actual.type.mro: # Try performing a quick check as an optimization return True diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 31a7d7d1e4d3..28792cb10ed5 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -22,6 +22,7 @@ from mypy import message_registry from mypy import subtypes from mypy import meet +from mypy.typeops import tuple_fallback MYPY = False if MYPY: # import for forward declaration only @@ -122,7 +123,10 @@ def _analyze_member_access(name: str, return analyze_type_callable_member_access(name, typ, mx) elif isinstance(typ, TypeType): return analyze_type_type_member_access(name, typ, mx) - elif isinstance(typ, (TupleType, TypedDictType, LiteralType, FunctionLike)): + elif isinstance(typ, TupleType): + # Actually look up from the fallback instance type. + return _analyze_member_access(name, tuple_fallback(typ), mx) + elif isinstance(typ, (TypedDictType, LiteralType, FunctionLike)): # Actually look up from the fallback instance type. return _analyze_member_access(name, typ.fallback, mx) elif isinstance(typ, NoneTyp): @@ -195,7 +199,7 @@ def analyze_type_callable_member_access(name: str, # TODO super? ret_type = typ.items()[0].ret_type if isinstance(ret_type, TupleType): - ret_type = ret_type.fallback + ret_type = tuple_fallback(ret_type) if isinstance(ret_type, Instance): if not mx.is_operator: # 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) if isinstance(typ.item.upper_bound, Instance): item = typ.item.upper_bound elif isinstance(typ.item, TupleType): - item = typ.item.fallback + item = tuple_fallback(typ.item) elif isinstance(typ.item, FunctionLike) and typ.item.is_type_obj(): item = typ.item.fallback elif isinstance(typ.item, TypeType): @@ -804,7 +808,7 @@ def map_type_from_supertype(typ: Type, # Create the type of self in subtype, of form t[a1, ...]. inst_type = fill_typevars(sub_info) if isinstance(inst_type, TupleType): - inst_type = inst_type.fallback + inst_type = tuple_fallback(inst_type) # Map the type of self to supertype. This gets us a description of the # supertype type variables in terms of subtype variables, i.e. t[t1, ...] # so that any type variables in tN are to be interpreted in subtype diff --git a/mypy/constraints.py b/mypy/constraints.py index 420d7f642bf1..8a4aaea61d0d 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -9,7 +9,8 @@ ) from mypy.maptype import map_instance_to_supertype import mypy.subtypes -from mypy.sametypes import is_same_type +import mypy.sametypes +import mypy.typeops from mypy.erasetype import erase_typevars from mypy.nodes import COVARIANT, CONTRAVARIANT from mypy.argmap import ArgTypeExpander @@ -199,7 +200,7 @@ def is_same_constraints(x: List[Constraint], y: List[Constraint]) -> bool: def is_same_constraint(c1: Constraint, c2: Constraint) -> bool: return (c1.type_var == c2.type_var and c1.op == c2.op - and is_same_type(c1.target, c2.target)) + and mypy.sametypes.is_same_type(c1.target, c2.target)) def simplify_away_incomplete_types(types: List[Type]) -> List[Type]: @@ -282,7 +283,8 @@ def visit_instance(self, template: Instance) -> List[Constraint]: if isinstance(actual, (CallableType, Overloaded)) and template.type.is_protocol: if template.type.protocol_members == ['__call__']: # Special case: a generic callback protocol - if not any(is_same_type(template, t) for t in template.type.inferring): + if not any(mypy.sametypes.is_same_type(template, t) + for t in template.type.inferring): template.type.inferring.append(template) call = mypy.subtypes.find_member('__call__', template, actual) assert call is not None @@ -340,7 +342,8 @@ def visit_instance(self, template: Instance) -> List[Constraint]: # Note that we use is_protocol_implementation instead of is_subtype # because some type may be considered a subtype of a protocol # due to _promote, but still not implement the protocol. - not any(is_same_type(template, t) for t in template.type.inferring) and + not any(mypy.sametypes.is_same_type(template, t) + for t in template.type.inferring) and mypy.subtypes.is_protocol_implementation(instance, erased)): template.type.inferring.append(template) self.infer_constraints_from_protocol_members(res, instance, template, @@ -349,7 +352,8 @@ def visit_instance(self, template: Instance) -> List[Constraint]: return res elif (instance.type.is_protocol and self.direction == SUBTYPE_OF and # We avoid infinite recursion for structural subtypes also here. - not any(is_same_type(instance, i) for i in instance.type.inferring) and + not any(mypy.sametypes.is_same_type(instance, i) + for i in instance.type.inferring) and mypy.subtypes.is_protocol_implementation(erased, instance)): instance.type.inferring.append(instance) self.infer_constraints_from_protocol_members(res, instance, template, @@ -370,7 +374,9 @@ def visit_instance(self, template: Instance) -> List[Constraint]: res.extend(cb) return res elif isinstance(actual, TupleType) and self.direction == SUPERTYPE_OF: - return infer_constraints(template, actual.fallback, self.direction) + return infer_constraints(template, + mypy.typeops.tuple_fallback(actual), + self.direction) else: return [] diff --git a/mypy/erasetype.py b/mypy/erasetype.py index fa3e4abf79b6..6027556cc0f9 100644 --- a/mypy/erasetype.py +++ b/mypy/erasetype.py @@ -73,7 +73,7 @@ def visit_overloaded(self, t: Overloaded) -> Type: return t.fallback.accept(self) def visit_tuple_type(self, t: TupleType) -> Type: - return t.fallback.accept(self) + return t.partial_fallback.accept(self) def visit_typeddict_type(self, t: TypedDictType) -> Type: return t.fallback.accept(self) diff --git a/mypy/fixup.py b/mypy/fixup.py index 37a723a3dbf7..e0165ed883eb 100644 --- a/mypy/fixup.py +++ b/mypy/fixup.py @@ -199,8 +199,8 @@ def visit_tuple_type(self, tt: TupleType) -> None: if tt.items: for it in tt.items: it.accept(self) - if tt.fallback is not None: - tt.fallback.accept(self) + if tt.partial_fallback is not None: + tt.partial_fallback.accept(self) def visit_typeddict_type(self, tdt: TypedDictType) -> None: if tdt.items: diff --git a/mypy/indirection.py b/mypy/indirection.py index 66b6d44f7c46..76241b75267a 100644 --- a/mypy/indirection.py +++ b/mypy/indirection.py @@ -85,7 +85,7 @@ def visit_overloaded(self, t: types.Overloaded) -> Set[str]: return self._visit(t.items()) | self._visit(t.fallback) def visit_tuple_type(self, t: types.TupleType) -> Set[str]: - return self._visit(t.items) | self._visit(t.fallback) + return self._visit(t.items) | self._visit(t.partial_fallback) def visit_typeddict_type(self, t: types.TypedDictType) -> Set[str]: return self._visit(t.items.values()) | self._visit(t.fallback) diff --git a/mypy/join.py b/mypy/join.py index 6212b508b563..a16e504a0515 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -14,6 +14,7 @@ is_protocol_implementation, find_member ) from mypy.nodes import ARG_NAMED, ARG_NAMED_OPT +import mypy.typeops from mypy import state @@ -243,7 +244,8 @@ def visit_tuple_type(self, t: TupleType) -> Type: items = [] # type: List[Type] for i in range(t.length()): items.append(self.join(t.items[i], self.s.items[i])) - fallback = join_instances(self.s.fallback, t.fallback) + fallback = join_instances(mypy.typeops.tuple_fallback(self.s), + mypy.typeops.tuple_fallback(t)) assert isinstance(fallback, Instance) return TupleType(items, fallback) else: @@ -299,7 +301,7 @@ def default(self, typ: Type) -> Type: elif isinstance(typ, UnboundType): return AnyType(TypeOfAny.special_form) elif isinstance(typ, TupleType): - return self.default(typ.fallback) + return self.default(mypy.typeops.tuple_fallback(typ)) elif isinstance(typ, TypedDictType): return self.default(typ.fallback) elif isinstance(typ, FunctionLike): diff --git a/mypy/meet.py b/mypy/meet.py index 10d5b051293a..a632ba67ecf0 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -15,6 +15,7 @@ ) from mypy.erasetype import erase_type from mypy.maptype import map_instance_to_supertype +from mypy.typeops import tuple_fallback from mypy import state # TODO Describe this module. @@ -211,9 +212,9 @@ def is_none_typevar_overlap(t1: Type, t2: Type) -> bool: if is_tuple(left) and is_tuple(right): return are_tuples_overlapping(left, right, ignore_promotions=ignore_promotions) elif isinstance(left, TupleType): - left = left.fallback + left = tuple_fallback(left) elif isinstance(right, TupleType): - right = right.fallback + right = tuple_fallback(right) # Next, we handle single-variant types that cannot be inherently partially overlapping, # but do require custom logic to inspect. @@ -515,7 +516,7 @@ def visit_tuple_type(self, t: TupleType) -> Type: for i in range(t.length()): items.append(self.meet(t.items[i], self.s.items[i])) # TODO: What if the fallbacks are different? - return TupleType(items, t.fallback) + return TupleType(items, tuple_fallback(t)) elif isinstance(self.s, Instance): # meet(Tuple[t1, t2, <...>], Tuple[s, ...]) == Tuple[meet(t1, s), meet(t2, s), <...>]. if self.s.type.fullname() == 'builtins.tuple' and self.s.args: diff --git a/mypy/messages.py b/mypy/messages.py index ce7a90ea32d1..80c5eaa665ec 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -208,8 +208,8 @@ def format_bare(self, typ: Type, verbosity: int = 0) -> str: return typ.name elif isinstance(typ, TupleType): # Prefer the name of the fallback class (if not tuple), as it's more informative. - if typ.fallback.type.fullname() != 'builtins.tuple': - return self.format_bare(typ.fallback) + if typ.partial_fallback.type.fullname() != 'builtins.tuple': + return self.format_bare(typ.partial_fallback) items = [] for t in typ.items: items.append(self.format_bare(t)) @@ -1225,7 +1225,11 @@ def report_protocol_problems(self, subtype: Union[Instance, TupleType, TypedDict # This will be only confusing a user even more. return - if isinstance(subtype, (TupleType, TypedDictType)): + if isinstance(subtype, TupleType): + if not isinstance(subtype.partial_fallback, Instance): + return + subtype = subtype.partial_fallback + elif isinstance(subtype, TypedDictType): if not isinstance(subtype.fallback, Instance): return subtype = subtype.fallback diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index 947fb921e23b..37649c9c970c 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -99,7 +99,9 @@ ) from mypy.util import get_prefix, correct_relative_import, unmangle, split_module_names from mypy.scope import Scope -from mypy.newsemanal.semanal_shared import SemanticAnalyzerInterface, set_callable_name +from mypy.newsemanal.semanal_shared import ( + SemanticAnalyzerInterface, set_callable_name, calculate_tuple_fallback, PRIORITY_FALLBACKS +) from mypy.newsemanal.semanal_namedtuple import NamedTupleAnalyzer from mypy.newsemanal.semanal_typeddict import TypedDictAnalyzer from mypy.newsemanal.semanal_enum import EnumCallAnalyzer @@ -1280,15 +1282,8 @@ def configure_base_classes(self, info.tuple_type = None for base, base_expr in bases: if isinstance(base, TupleType): - if info.tuple_type: - self.fail("Class has two incompatible bases derived from tuple", defn) - defn.has_incompatible_baseclass = True - info.tuple_type = base - base_types.append(base.fallback) - if isinstance(base_expr, CallExpr): - defn.analyzed = NamedTupleExpr(base.fallback.type) - defn.analyzed.line = defn.line - defn.analyzed.column = defn.column + actual_base = self.configure_tuple_base_class(defn, base, base_expr) + base_types.append(actual_base) elif isinstance(base, Instance): if base.type.is_newtype: self.fail("Cannot subclass NewType", defn) @@ -1319,17 +1314,37 @@ def configure_base_classes(self, info.bases = base_types - # Calculate the MRO. It might be incomplete at this point if - # the bases of defn include classes imported from other - # modules in an import loop. We'll recompute it in SemanticAnalyzerPass3. + # Calculate the MRO. if not self.verify_base_classes(defn): # Give it an MRO consisting of just the class itself and object. defn.info.mro = [defn.info, self.object_type().type] return - # TODO: Ideally we should move MRO calculation to a later stage, but this is - # not easy, see issue #5536. self.calculate_class_mro(defn, self.object_type) + def configure_tuple_base_class(self, + defn: ClassDef, + base: TupleType, + base_expr: Expression) -> Instance: + info = defn.info + + # There may be an existing valid tuple type from previous semanal iterations. + # Use equality to check if it is the case. + if info.tuple_type and info.tuple_type != base: + self.fail("Class has two incompatible bases derived from tuple", defn) + defn.has_incompatible_baseclass = True + info.tuple_type = base + if isinstance(base_expr, CallExpr): + defn.analyzed = NamedTupleExpr(base.partial_fallback.type) + defn.analyzed.line = defn.line + defn.analyzed.column = defn.column + + if base.partial_fallback.type.fullname() == 'builtins.tuple': + # Fallback can only be safely calculated after semantic analysis, since base + # classes may be incomplete. Postpone the calculation. + self.schedule_patch(PRIORITY_FALLBACKS, lambda: calculate_tuple_fallback(base)) + + return base.partial_fallback + def calculate_class_mro(self, defn: ClassDef, obj_type: Optional[Callable[[], Instance]] = None) -> None: """Calculate method resolution order for a class. diff --git a/mypy/newsemanal/semanal_main.py b/mypy/newsemanal/semanal_main.py index 5244aca0a25f..45b7f363a4bd 100644 --- a/mypy/newsemanal/semanal_main.py +++ b/mypy/newsemanal/semanal_main.py @@ -24,14 +24,14 @@ will be incomplete. """ -from typing import List, Tuple, Optional, Union +from typing import List, Tuple, Optional, Union, Callable from mypy.nodes import ( Node, SymbolTable, SymbolNode, MypyFile, TypeInfo, FuncDef, Decorator, OverloadedFuncDef ) from mypy.newsemanal.semanal_typeargs import TypeArgumentAnalyzer from mypy.state import strict_optional_set -from mypy.newsemanal.semanal import NewSemanticAnalyzer +from mypy.newsemanal.semanal import NewSemanticAnalyzer, apply_semantic_analyzer_patches from mypy.newsemanal.semanal_abstract import calculate_abstract_status from mypy.errors import Errors from mypy.newsemanal.semanal_infer import infer_decorator_signature_if_simple @@ -41,6 +41,9 @@ from mypy.build import Graph, State +Patches = List[Tuple[int, Callable[[], None]]] + + # Perform up to this many semantic analysis iterations until giving up trying to bind all names. MAX_ITERATIONS = 10 @@ -54,16 +57,21 @@ def semantic_analysis_for_scc(graph: 'Graph', scc: List[str], errors: Errors) -> Assume that reachability analysis has already been performed. """ + patches = [] # type: Patches # Note that functions can't define new module-level attributes # using 'global x', since module top levels are fully processed # before functions. This limitation is unlikely to go away soon. - process_top_levels(graph, scc) - process_functions(graph, scc) + process_top_levels(graph, scc, patches) + process_functions(graph, scc, patches) + # We use patch callbacks to fix up things when we expect relatively few + # callbacks to be required. + apply_semantic_analyzer_patches(patches) + # This pass might need fallbacks calculated above. check_type_arguments(graph, scc, errors) process_abstract_status(graph, scc, errors) -def process_top_levels(graph: 'Graph', scc: List[str]) -> None: +def process_top_levels(graph: 'Graph', scc: List[str], patches: Patches) -> None: # Process top levels until everything has been bound. # Initialize ASTs and symbol tables. @@ -96,7 +104,7 @@ def process_top_levels(graph: 'Graph', scc: List[str]) -> None: state = graph[next_id] assert state.tree is not None deferred, incomplete = semantic_analyze_target(next_id, state, state.tree, None, - final_iteration) + final_iteration, patches) all_deferred += deferred if not incomplete: state.manager.incomplete_namespaces.discard(next_id) @@ -105,7 +113,7 @@ def process_top_levels(graph: 'Graph', scc: List[str]) -> None: worklist = list(reversed(all_deferred)) -def process_functions(graph: 'Graph', scc: List[str]) -> None: +def process_functions(graph: 'Graph', scc: List[str], patches: Patches) -> None: # Process functions. for module in scc: tree = graph[module].tree @@ -115,7 +123,13 @@ def process_functions(graph: 'Graph', scc: List[str]) -> None: targets = get_all_leaf_targets(symtable, module, None) for target, node, active_type in targets: assert isinstance(node, (FuncDef, OverloadedFuncDef, Decorator)) - process_top_level_function(analyzer, graph[module], module, target, node, active_type) + process_top_level_function(analyzer, + graph[module], + module, + target, + node, + active_type, + patches) def process_top_level_function(analyzer: 'NewSemanticAnalyzer', @@ -123,7 +137,8 @@ def process_top_level_function(analyzer: 'NewSemanticAnalyzer', module: str, target: str, node: Union[FuncDef, OverloadedFuncDef, Decorator], - active_type: Optional[TypeInfo]) -> None: + active_type: Optional[TypeInfo], + patches: Patches) -> None: """Analyze single top-level function or method. Process the body of the function (including nested functions) again and again, @@ -143,7 +158,7 @@ def process_top_level_function(analyzer: 'NewSemanticAnalyzer', more_iterations = False analyzer.incomplete_namespaces.discard(module) deferred, incomplete = semantic_analyze_target(target, state, node, active_type, - not more_iterations) + not more_iterations, patches) analyzer.incomplete_namespaces.discard(module) # After semantic analysis is done, discard local namespaces @@ -178,7 +193,8 @@ def semantic_analyze_target(target: str, state: 'State', node: Union[MypyFile, FuncDef, OverloadedFuncDef, Decorator], active_type: Optional[TypeInfo], - final_iteration: bool) -> Tuple[List[str], bool]: + final_iteration: bool, + patches: Patches) -> Tuple[List[str], bool]: tree = state.tree assert tree is not None analyzer = state.manager.new_semantic_analyzer @@ -195,7 +211,7 @@ def semantic_analyze_target(target: str, if isinstance(refresh_node, Decorator): # Decorator expressions will be processed as part of the module top level. refresh_node = refresh_node.func - analyzer.refresh_partial(refresh_node, [], final_iteration) + analyzer.refresh_partial(refresh_node, patches, final_iteration) if isinstance(node, Decorator): infer_decorator_signature_if_simple(node, analyzer) if analyzer.deferred: diff --git a/mypy/newsemanal/semanal_namedtuple.py b/mypy/newsemanal/semanal_namedtuple.py index 08152ab3cc2b..4d2c462732ee 100644 --- a/mypy/newsemanal/semanal_namedtuple.py +++ b/mypy/newsemanal/semanal_namedtuple.py @@ -1,7 +1,8 @@ """Semantic analysis of named tuple definitions. -This is conceptually part of mypy.semanal (semantic analyzer pass 2). +This is conceptually part of mypy.newsemanal.semanal. """ + from contextlib import contextmanager from typing import Tuple, List, Dict, Mapping, Optional, Union, cast, Iterator @@ -9,7 +10,7 @@ Type, TupleType, AnyType, TypeOfAny, TypeVarDef, CallableType, TypeType, TypeVarType ) from mypy.newsemanal.semanal_shared import ( - SemanticAnalyzerInterface, set_callable_name, PRIORITY_FALLBACKS + SemanticAnalyzerInterface, set_callable_name, calculate_tuple_fallback, PRIORITY_FALLBACKS ) from mypy.nodes import ( Var, EllipsisExpr, Argument, StrExpr, BytesExpr, UnicodeExpr, ExpressionStmt, NameExpr, @@ -19,7 +20,6 @@ ) from mypy.options import Options from mypy.exprtotype import expr_to_unanalyzed_type, TypeTranslationError -from mypy import join MYPY = False if MYPY: @@ -364,17 +364,14 @@ def build_namedtuple_typeinfo(self, name: str, items: List[str], types: List[Typ info = self.api.basic_new_typeinfo(name, fallback) info.is_named_tuple = True - info.tuple_type = TupleType(types, fallback) - - def patch() -> None: - # Calculate the correct value type for the fallback tuple. - assert info.tuple_type, "TupleType type deleted before calling the patch" - fallback.args[0] = join.join_type_list(list(info.tuple_type.items)) + tuple_base = TupleType(types, fallback) + info.tuple_type = tuple_base # We can't calculate the complete fallback type until after semantic - # analysis, since otherwise MROs might be incomplete. Postpone a callback - # function that patches the fallback. - self.api.schedule_patch(PRIORITY_FALLBACKS, patch) + # analysis, since otherwise base classes might be incomplete. Postpone a + # callback function that patches the fallback. + self.api.schedule_patch(PRIORITY_FALLBACKS, + lambda: calculate_tuple_fallback(tuple_base)) def add_field(var: Var, is_initialized_in_class: bool = False, is_property: bool = False) -> None: diff --git a/mypy/newsemanal/semanal_newtype.py b/mypy/newsemanal/semanal_newtype.py index c4734026dcd1..d68c7c5107d7 100644 --- a/mypy/newsemanal/semanal_newtype.py +++ b/mypy/newsemanal/semanal_newtype.py @@ -40,7 +40,8 @@ def process_newtype_declaration(self, s: AssignmentStmt) -> None: # Create the corresponding class definition if the aliased type is subtypeable if isinstance(old_type, TupleType): - newtype_class_info = self.build_newtype_typeinfo(name, old_type, old_type.fallback) + newtype_class_info = self.build_newtype_typeinfo(name, old_type, + old_type.partial_fallback) newtype_class_info.tuple_type = old_type elif isinstance(old_type, Instance): if old_type.type.is_protocol: diff --git a/mypy/newsemanal/semanal_pass3.py b/mypy/newsemanal/semanal_pass3.py index 72836afcd50d..945e7cee1212 100644 --- a/mypy/newsemanal/semanal_pass3.py +++ b/mypy/newsemanal/semanal_pass3.py @@ -28,11 +28,7 @@ from mypy.options import Options from mypy.traverser import TraverserVisitor from mypy.newsemanal.typeanal import TypeAnalyserPass3, collect_any_types -from mypy.typevars import has_no_typevars -from mypy.newsemanal.semanal_shared import PRIORITY_FORWARD_REF, PRIORITY_TYPEVAR_VALUES from mypy.newsemanal.semanal import NewSemanticAnalyzer -from mypy.subtypes import is_subtype -from mypy.sametypes import is_same_type from mypy.scope import Scope from mypy.newsemanal.semanal_shared import SemanticAnalyzerCoreInterface diff --git a/mypy/newsemanal/semanal_shared.py b/mypy/newsemanal/semanal_shared.py index 9aba20be3ee2..82d5d1889a6a 100644 --- a/mypy/newsemanal/semanal_shared.py +++ b/mypy/newsemanal/semanal_shared.py @@ -9,22 +9,19 @@ SymbolNode ) from mypy.util import correct_relative_import -from mypy.types import Type, FunctionLike, Instance +from mypy.types import Type, FunctionLike, Instance, TupleType from mypy.tvar_scope import TypeVarScope +from mypy import join MYPY = False if False: from typing_extensions import Final -# Priorities for ordering of patches within the final "patch" phase of semantic analysis -# (after pass 3): +# Priorities for ordering of patches within the "patch" phase of semantic analysis +# (after the main pass): -# Fix forward references (needs to happen first) -PRIORITY_FORWARD_REF = 0 # type: Final # Fix fallbacks (does joins) PRIORITY_FALLBACKS = 1 # type: Final -# Checks type var values (does subtype checks) -PRIORITY_TYPEVAR_VALUES = 2 # type: Final @trait @@ -190,3 +187,22 @@ def set_callable_name(sig: Type, fdef: FuncDef) -> Type: return sig.with_name(fdef.name()) else: return sig + + +def calculate_tuple_fallback(typ: TupleType) -> None: + """Calculate a precise item type for the fallback of a tuple type. + + This must be called only after the main semantic analysis pass, since joins + aren't available before that. + + Note that there is an apparent chicken and egg problem with respect + to verifying type arguments against bounds. Verifying bounds might + require fallbacks, but we might use the bounds to calculate the + fallbacks. In partice this is not a problem, since the worst that + can happen is that we have invalid type argument values, and these + can happen in later stages as well (they will generate errors, but + we don't prevent their existence). + """ + fallback = typ.partial_fallback + assert fallback.type.fullname() == 'builtins.tuple' + fallback.args[0] = join.join_type_list(list(typ.items)) diff --git a/mypy/newsemanal/typeanal.py b/mypy/newsemanal/typeanal.py index 1376b1ec0363..31734adbaaef 100644 --- a/mypy/newsemanal/typeanal.py +++ b/mypy/newsemanal/typeanal.py @@ -493,7 +493,7 @@ def visit_tuple_type(self, t: TupleType) -> Type: return AnyType(TypeOfAny.from_error) any_type = AnyType(TypeOfAny.special_form) # If the fallback isn't filled in yet, its type will be the falsey FakeInfo - fallback = (t.fallback if t.fallback.type + fallback = (t.partial_fallback if t.partial_fallback.type else self.named_type('builtins.tuple', [any_type])) return TupleType(self.anal_array(t.items), fallback, t.line) diff --git a/mypy/sametypes.py b/mypy/sametypes.py index 1fec6d572fd4..a4e603458390 100644 --- a/mypy/sametypes.py +++ b/mypy/sametypes.py @@ -5,6 +5,7 @@ UnionType, CallableType, TypeVarType, Instance, TypeVisitor, ErasedType, Overloaded, PartialType, DeletedType, UninhabitedType, TypeType, LiteralType, ) +from mypy.typeops import tuple_fallback def is_same_type(left: Type, right: Type) -> bool: @@ -99,7 +100,7 @@ def visit_callable_type(self, left: CallableType) -> bool: def visit_tuple_type(self, left: TupleType) -> bool: if isinstance(self.right, TupleType): - return (is_same_type(left.fallback, self.right.fallback) + return (is_same_type(tuple_fallback(left), tuple_fallback(self.right)) and is_same_types(left.items, self.right.items)) else: return False diff --git a/mypy/semanal.py b/mypy/semanal.py index 76617685d6c8..20ee6866a0c4 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1179,9 +1179,9 @@ def analyze_base_classes(self, defn: ClassDef) -> None: self.fail("Class has two incompatible bases derived from tuple", defn) defn.has_incompatible_baseclass = True info.tuple_type = base - base_types.append(base.fallback) + base_types.append(base.partial_fallback) if isinstance(base_expr, CallExpr): - defn.analyzed = NamedTupleExpr(base.fallback.type) + defn.analyzed = NamedTupleExpr(base.partial_fallback.type) defn.analyzed.line = defn.line defn.analyzed.column = defn.column elif isinstance(base, Instance): diff --git a/mypy/semanal_newtype.py b/mypy/semanal_newtype.py index 034dea1f6403..6bb2dccd081c 100644 --- a/mypy/semanal_newtype.py +++ b/mypy/semanal_newtype.py @@ -40,7 +40,8 @@ def process_newtype_declaration(self, s: AssignmentStmt) -> None: # Create the corresponding class definition if the aliased type is subtypeable if isinstance(old_type, TupleType): - newtype_class_info = self.build_newtype_typeinfo(name, old_type, old_type.fallback) + newtype_class_info = self.build_newtype_typeinfo(name, old_type, + old_type.partial_fallback) newtype_class_info.tuple_type = old_type elif isinstance(old_type, Instance): if old_type.type.is_protocol: diff --git a/mypy/semanal_pass3.py b/mypy/semanal_pass3.py index f31742a503b9..835d61564ead 100644 --- a/mypy/semanal_pass3.py +++ b/mypy/semanal_pass3.py @@ -356,7 +356,7 @@ def perform_transform(self, node: Node, transform: Callable[[Type], Type]) -> No if isinstance(new_b, Instance): new_bases.append(new_b) elif isinstance(new_b, TupleType): - new_bases.append(new_b.fallback) + new_bases.append(new_b.partial_fallback) else: self.fail("Argument 2 to NewType(...) must be subclassable" " (got {})".format(new_b), node) @@ -696,7 +696,7 @@ def visit_tuple_type(self, t: TupleType) -> Type: if self.check_recursion(t): return AnyType(TypeOfAny.from_error) items = [it.accept(self) for it in t.items] - fallback = self.visit_instance(t.fallback, from_fallback=True) + fallback = self.visit_instance(t.partial_fallback, from_fallback=True) assert isinstance(fallback, Instance) return TupleType(items, fallback, t.line, t.column) diff --git a/mypy/server/astmerge.py b/mypy/server/astmerge.py index 39865efa2faf..dcfd9231306f 100644 --- a/mypy/server/astmerge.py +++ b/mypy/server/astmerge.py @@ -383,8 +383,8 @@ def visit_tuple_type(self, typ: TupleType) -> None: for item in typ.items: item.accept(self) # Fallback can be None for implicit tuple types that haven't been semantically analyzed. - if typ.fallback is not None: - typ.fallback.accept(self) + if typ.partial_fallback is not None: + typ.partial_fallback.accept(self) def visit_type_type(self, typ: TypeType) -> None: typ.item.accept(self) diff --git a/mypy/server/deps.py b/mypy/server/deps.py index 43fb464fda5e..48704304a452 100644 --- a/mypy/server/deps.py +++ b/mypy/server/deps.py @@ -741,7 +741,7 @@ def add_operator_method_dependency_for_type(self, typ: Type, method: str) -> Non if isinstance(typ, TypeVarType): typ = typ.upper_bound if isinstance(typ, TupleType): - typ = typ.fallback + typ = typ.partial_fallback if isinstance(typ, Instance): trigger = make_trigger(typ.type.fullname() + '.' + method) self.add_dependency(trigger) @@ -823,7 +823,7 @@ def attribute_triggers(self, typ: Type, name: str) -> List[str]: if isinstance(typ, TypeVarType): typ = typ.upper_bound if isinstance(typ, TupleType): - typ = typ.fallback + typ = typ.partial_fallback if isinstance(typ, Instance): member = '%s.%s' % (typ.type.fullname(), name) return [make_trigger(member)] @@ -919,7 +919,7 @@ def visit_tuple_type(self, typ: TupleType) -> List[str]: triggers = [] for item in typ.items: triggers.extend(self.get_type_triggers(item)) - triggers.extend(self.get_type_triggers(typ.fallback)) + triggers.extend(self.get_type_triggers(typ.partial_fallback)) return triggers def visit_type_type(self, typ: TypeType) -> List[str]: diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 83ebd468b9b3..a5f5ccbd01eb 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -9,17 +9,18 @@ ) import mypy.applytype import mypy.constraints +import mypy.typeops +import mypy.sametypes from mypy.erasetype import erase_type # Circular import; done in the function instead. # import mypy.solve -from mypy import messages, sametypes +from mypy import messages from mypy.nodes import ( FuncBase, Var, Decorator, OverloadedFuncDef, TypeInfo, CONTRAVARIANT, COVARIANT, ARG_POS, ARG_OPT, ARG_STAR, ARG_STAR2 ) from mypy.maptype import map_instance_to_supertype from mypy.expandtype import expand_type_by_instance -from mypy.sametypes import is_same_type from mypy.typestate import TypeState, SubtypeKind from mypy import state @@ -191,8 +192,8 @@ def visit_instance(self, left: Instance) -> bool: return False return True right = self.right - if isinstance(right, TupleType) and right.fallback.type.is_enum: - return self._is_subtype(left, right.fallback) + if isinstance(right, TupleType) and mypy.typeops.tuple_fallback(right).type.is_enum: + return self._is_subtype(left, mypy.typeops.tuple_fallback(right)) if isinstance(right, Instance): if TypeState.is_cached_subtype_check(self._subtype_kind, left, right): return True @@ -220,7 +221,7 @@ def visit_instance(self, left: Instance) -> bool: if isinstance(right, TypeType): item = right.item if isinstance(item, TupleType): - item = item.fallback + item = mypy.typeops.tuple_fallback(item) if is_named_instance(left, 'builtins.type'): return self._is_subtype(TypeType(AnyType(TypeOfAny.special_form)), right) if left.type.is_metaclass(): @@ -284,7 +285,7 @@ def visit_tuple_type(self, left: TupleType) -> bool: else: iter_type = AnyType(TypeOfAny.special_form) return all(self._is_subtype(li, iter_type) for li in left.items) - elif self._is_subtype(left.fallback, right): + elif self._is_subtype(mypy.typeops.tuple_fallback(left), right): return True return False elif isinstance(right, TupleType): @@ -293,7 +294,15 @@ def visit_tuple_type(self, left: TupleType) -> bool: for l, r in zip(left.items, right.items): if not self._is_subtype(l, r): return False - if not self._is_subtype(left.fallback, right.fallback): + rfallback = mypy.typeops.tuple_fallback(right) + if is_named_instance(rfallback, 'builtins.tuple'): + # No need to verify fallback. This is useful since the calculated fallback + # may be inconsistent due to how we calculate joins between unions vs. + # non-unions. For example, join(int, str) == object, whereas + # join(Union[int, C], Union[str, C]) == Union[int, str, C]. + return True + lfallback = mypy.typeops.tuple_fallback(left) + if not self._is_subtype(lfallback, rfallback): return False return True else: @@ -458,7 +467,8 @@ def f(self) -> A: ... TypeState.record_protocol_subtype_check(left.type, right.type) assuming = right.type.assuming_proper if proper_subtype else right.type.assuming for (l, r) in reversed(assuming): - if sametypes.is_same_type(l, left) and sametypes.is_same_type(r, right): + if (mypy.sametypes.is_same_type(l, left) + and mypy.sametypes.is_same_type(r, right)): return True with pop_on_exit(assuming, left, right): for member in right.type.protocol_members: @@ -1095,7 +1105,7 @@ def check_argument(leftarg: Type, rightarg: Type, variance: int) -> bool: elif variance == CONTRAVARIANT: return self._is_proper_subtype(rightarg, leftarg) else: - return sametypes.is_same_type(leftarg, rightarg) + return mypy.sametypes.is_same_type(leftarg, rightarg) # Map left type to corresponding right instances. left = map_instance_to_supertype(left, right.type) @@ -1153,21 +1163,23 @@ def visit_tuple_type(self, left: TupleType) -> bool: # for isinstance(x, tuple), though it's unclear why. return True return all(self._is_proper_subtype(li, iter_type) for li in left.items) - return self._is_proper_subtype(left.fallback, right) + return self._is_proper_subtype(mypy.typeops.tuple_fallback(left), right) elif isinstance(right, TupleType): if len(left.items) != len(right.items): return False for l, r in zip(left.items, right.items): if not self._is_proper_subtype(l, r): return False - return self._is_proper_subtype(left.fallback, right.fallback) + return self._is_proper_subtype(mypy.typeops.tuple_fallback(left), + mypy.typeops.tuple_fallback(right)) return False def visit_typeddict_type(self, left: TypedDictType) -> bool: right = self.right if isinstance(right, TypedDictType): for name, typ in left.items.items(): - if name in right.items and not is_same_type(typ, right.items[name]): + if (name in right.items + and not mypy.sametypes.is_same_type(typ, right.items[name])): return False for name, typ in right.items.items(): if name not in left.items: diff --git a/mypy/type_visitor.py b/mypy/type_visitor.py index bd1e7a33333f..d17b54192262 100644 --- a/mypy/type_visitor.py +++ b/mypy/type_visitor.py @@ -190,7 +190,7 @@ def visit_callable_type(self, t: CallableType) -> Type: def visit_tuple_type(self, t: TupleType) -> Type: return TupleType(self.translate_types(t.items), # TODO: This appears to be unsafe. - cast(Any, t.fallback.accept(self)), + cast(Any, t.partial_fallback.accept(self)), t.line, t.column) def visit_typeddict_type(self, t: TypedDictType) -> Type: diff --git a/mypy/typeanal.py b/mypy/typeanal.py index fe7ca64d5b4f..de5ce7329bf4 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -483,7 +483,7 @@ def visit_tuple_type(self, t: TupleType) -> Type: return AnyType(TypeOfAny.from_error) any_type = AnyType(TypeOfAny.special_form) # If the fallback isn't filled in yet, its type will be the falsey FakeInfo - fallback = (t.fallback if t.fallback.type + fallback = (t.partial_fallback if t.partial_fallback.type else self.named_type('builtins.tuple', [any_type])) return TupleType(self.anal_array(t.items), fallback, t.line) diff --git a/mypy/typeops.py b/mypy/typeops.py new file mode 100644 index 000000000000..44630ce95903 --- /dev/null +++ b/mypy/typeops.py @@ -0,0 +1,19 @@ +"""Miscellaneuus type operations and helpers for use during type checking. + +NOTE: These must not be accessed from mypy.nodes or mypy.types to avoid import + cycles. These must not be called from the semantic analysis main pass + since these may assume that MROs are ready. +""" + +# TODO: Move more type operations here + +from mypy.types import TupleType, Instance +from mypy.join import join_type_list + + +def tuple_fallback(typ: TupleType) -> Instance: + """Return fallback type for a tuple.""" + info = typ.partial_fallback.type + if info.fullname() != 'builtins.tuple': + return typ.partial_fallback + return Instance(info, [join_type_list(typ.items)]) diff --git a/mypy/types.py b/mypy/types.py index c9fd0e52dc80..6695229d6732 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -919,7 +919,7 @@ def type_object(self) -> mypy.nodes.TypeInfo: assert self.is_type_obj() ret = self.ret_type if isinstance(ret, TupleType): - ret = ret.fallback + ret = ret.partial_fallback if isinstance(ret, TypeVarType): ret = ret.upper_bound assert isinstance(ret, Instance) @@ -1173,22 +1173,24 @@ class TupleType(Type): """The tuple type Tuple[T1, ..., Tn] (at least one type argument). Instance variables: - items: tuple item types - fallback: the underlying instance type that is used for non-tuple methods - (this is currently always builtins.tuple, but it could be different for named - tuples, for example) - implicit: if True, derived from a tuple expression (t,....) instead of Tuple[t, ...] + items: Tuple item types + partial_fallback: The (imprecise) underlying instance type that is used + for non-tuple methods. This is generally builtins.tuple[Any] for + regular tuples, but it's different for named tuples and classes with + a tuple base class. Use mypy.typeops.tuple_fallback to calculate the + precise fallback type derived from item types. + implicit: If True, derived from a tuple expression (t,....) instead of Tuple[t, ...] """ items = None # type: List[Type] - fallback = None # type: Instance + partial_fallback = None # type: Instance implicit = False def __init__(self, items: List[Type], fallback: Instance, line: int = -1, column: int = -1, implicit: bool = False) -> None: super().__init__(line, column) self.items = items - self.fallback = fallback + self.partial_fallback = fallback self.implicit = implicit self.can_be_true = len(self.items) > 0 self.can_be_false = len(self.items) == 0 @@ -1200,17 +1202,17 @@ def accept(self, visitor: 'TypeVisitor[T]') -> T: return visitor.visit_tuple_type(self) def __hash__(self) -> int: - return hash((tuple(self.items), self.fallback)) + return hash((tuple(self.items), self.partial_fallback)) def __eq__(self, other: object) -> bool: if not isinstance(other, TupleType): return NotImplemented - return self.items == other.items and self.fallback == other.fallback + return self.items == other.items and self.partial_fallback == other.partial_fallback def serialize(self) -> JsonDict: return {'.class': 'TupleType', 'items': [t.serialize() for t in self.items], - 'fallback': self.fallback.serialize(), + 'partial_fallback': self.partial_fallback.serialize(), 'implicit': self.implicit, } @@ -1218,20 +1220,20 @@ def serialize(self) -> JsonDict: def deserialize(cls, data: JsonDict) -> 'TupleType': assert data['.class'] == 'TupleType' return TupleType([deserialize_type(t) for t in data['items']], - Instance.deserialize(data['fallback']), + Instance.deserialize(data['partial_fallback']), implicit=data['implicit']) def copy_modified(self, *, fallback: Optional[Instance] = None, items: Optional[List[Type]] = None) -> 'TupleType': if fallback is None: - fallback = self.fallback + fallback = self.partial_fallback if items is None: items = self.items return TupleType(items, fallback, self.line, self.column) def slice(self, begin: Optional[int], stride: Optional[int], end: Optional[int]) -> 'TupleType': - return TupleType(self.items[begin:end:stride], self.fallback, + return TupleType(self.items[begin:end:stride], self.partial_fallback, self.line, self.column, self.implicit) @@ -1955,10 +1957,10 @@ def visit_overloaded(self, t: Overloaded) -> str: def visit_tuple_type(self, t: TupleType) -> str: s = self.list_str(t.items) - if t.fallback and t.fallback.type: - fallback_name = t.fallback.type.fullname() + if t.partial_fallback and t.partial_fallback.type: + fallback_name = t.partial_fallback.type.fullname() if fallback_name != 'builtins.tuple': - return 'Tuple[{}, fallback={}]'.format(s, t.fallback.accept(self)) + return 'Tuple[{}, fallback={}]'.format(s, t.partial_fallback.accept(self)) return 'Tuple[{}]'.format(s) def visit_typeddict_type(self, t: TypedDictType) -> str: diff --git a/mypy/typetraverser.py b/mypy/typetraverser.py index c0d9865801f1..8a3ad645d198 100644 --- a/mypy/typetraverser.py +++ b/mypy/typetraverser.py @@ -53,7 +53,7 @@ def visit_callable_type(self, t: CallableType) -> None: def visit_tuple_type(self, t: TupleType) -> None: self.traverse_types(t.items) - t.fallback.accept(self) + t.partial_fallback.accept(self) def visit_typeddict_type(self, t: TypedDictType) -> None: self.traverse_types(t.items.values()) diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index 8ea40895f5c8..73cf027c1171 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -1580,6 +1580,65 @@ class C: def C() -> None: # E: Name 'C' already defined on line 1 ''() # E: "str" not callable +[case testNewAnalyzerTupleIteration] +from typing import Union, Tuple, NamedTuple + +class T(Tuple[B, C]): + pass + +class A: pass +class B(A): pass +class C(A): pass + +class NTInt(NamedTuple): + x: int + y: int + +class NTStr(NamedTuple): + x: str + y: str + +t1: T +reveal_type(t1.__iter__) # E: Revealed type is 'def () -> typing.Iterator[__main__.A*]' + +t2: NTInt +reveal_type(t2.__iter__) # E: Revealed type is 'def () -> typing.Iterator[builtins.int*]' +nt: Union[NTInt, NTStr] +reveal_type(nt.__iter__) # E: Revealed type is 'Union[def () -> typing.Iterator[builtins.int*], def () -> typing.Iterator[builtins.str*]]' +for nx in nt: + reveal_type(nx) # E: Revealed type is 'Union[builtins.int*, builtins.str*]' + +t: Union[Tuple[int, int], Tuple[str, str]] +for x in t: + reveal_type(x) # E: Revealed type is 'Union[builtins.int*, builtins.str*]' +[builtins fixtures/for.pyi] +[out] + +[case testNewAnalyzerFallbackUpperBoundCheckAndFallbacks] +from typing import TypeVar, Generic, Tuple + +class A: pass +class B: pass +class C(B): pass + +S = TypeVar('S', bound=Tuple[G[A], ...]) + +class GG(Generic[S]): pass + +g: GG[Tuple[G[B], G[C]]] \ + # E: Type argument "__main__.B" of "G" must be a subtype of "__main__.A" \ + # E: Type argument "__main__.C" of "G" must be a subtype of "__main__.A" \ + # E: Type argument "Tuple[__main__.G[__main__.B], __main__.G[__main__.C]]" of "GG" must be a subtype of "builtins.tuple[__main__.G[__main__.A]]" + +T = TypeVar('T', bound=A, covariant=True) + +class G(Generic[T]): pass + +t: Tuple[G[B], G[C]] # E: Type argument "__main__.B" of "G" must be a subtype of "__main__.A" \ + # E: Type argument "__main__.C" of "G" must be a subtype of "__main__.A" +reveal_type(t.__iter__) # E: Revealed type is 'def () -> typing.Iterator[__main__.G*[__main__.B]]' +[builtins fixtures/tuple.pyi] + [case testNewAnalyzerClassKeywordsForward] class C(B, other=A): ... class B: ... diff --git a/test-data/unit/check-unions.test b/test-data/unit/check-unions.test index da90bc6dcd2e..6ce0fb3d8d37 100644 --- a/test-data/unit/check-unions.test +++ b/test-data/unit/check-unions.test @@ -782,14 +782,16 @@ class NTStr(NamedTuple): x: str y: str +t1: NTInt +reveal_type(t1.__iter__) # E: Revealed type is 'def () -> typing.Iterator[builtins.int*]' nt: Union[NTInt, NTStr] +reveal_type(nt.__iter__) # E: Revealed type is 'Union[def () -> typing.Iterator[builtins.int*], def () -> typing.Iterator[builtins.str*]]' for nx in nt: reveal_type(nx) # E: Revealed type is 'Union[builtins.int*, builtins.str*]' t: Union[Tuple[int, int], Tuple[str, str]] for x in t: - # TODO(Ivan): This will be OK when tuple fallback patches are added (like above) - reveal_type(x) # E: Revealed type is 'Any' + reveal_type(x) # E: Revealed type is 'Union[builtins.int*, builtins.str*]' [builtins fixtures/for.pyi] [out]