diff --git a/mypy/build.py b/mypy/build.py index 22e5d59b7204..46fe028eb5d2 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -491,7 +491,7 @@ def __init__(self, data_dir: str, self.semantic_analyzer = SemanticAnalyzer(self.modules, self.missing_modules, lib_path, self.errors, self.plugin) self.modules = self.semantic_analyzer.modules - self.semantic_analyzer_pass3 = ThirdPass(self.modules, self.errors) + self.semantic_analyzer_pass3 = ThirdPass(self.modules, self.errors, self.semantic_analyzer) self.all_types = {} # type: Dict[Expression, Type] self.indirection_detector = TypeIndirectionVisitor() self.stale_modules = set() # type: Set[str] @@ -1722,10 +1722,13 @@ def semantic_analysis(self) -> None: def semantic_analysis_pass_three(self) -> None: assert self.tree is not None, "Internal error: method must be called on parsed file only" + patches = [] # type: List[Callable[[], None]] with self.wrap_context(): - self.manager.semantic_analyzer_pass3.visit_file(self.tree, self.xpath, self.options) + self.manager.semantic_analyzer_pass3.visit_file(self.tree, self.xpath, + self.options, patches) if self.options.dump_type_stats: dump_type_stats(self.tree, self.xpath) + self.patches = patches + self.patches def semantic_analysis_apply_patches(self) -> None: for patch_func in self.patches: diff --git a/mypy/indirection.py b/mypy/indirection.py index 2e69c5ebd3ff..badbe38cae38 100644 --- a/mypy/indirection.py +++ b/mypy/indirection.py @@ -101,3 +101,6 @@ def visit_ellipsis_type(self, t: types.EllipsisType) -> Set[str]: def visit_type_type(self, t: types.TypeType) -> Set[str]: return self._visit(t.item) + + def visit_forwardref_type(self, t: types.ForwardRef) -> Set[str]: + return self._visit(t.link) diff --git a/mypy/messages.py b/mypy/messages.py index 2f3473b3e59d..5fe0f33ce591 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -20,7 +20,7 @@ from mypy.types import ( Type, CallableType, Instance, TypeVarType, TupleType, TypedDictType, UnionType, NoneTyp, AnyType, Overloaded, FunctionLike, DeletedType, TypeType, - UninhabitedType, TypeOfAny + UninhabitedType, TypeOfAny, ForwardRef, UnboundType ) from mypy.nodes import ( TypeInfo, Context, MypyFile, op_methods, FuncDef, reverse_type_aliases, @@ -194,7 +194,7 @@ def quote_type_string(self, type_string: str) -> str: """Quotes a type representation for use in messages.""" no_quote_regex = r'^<(tuple|union): \d+ items>$' if (type_string in ['Module', 'overloaded function', '', ''] - or re.match(no_quote_regex, type_string) is not None): + or re.match(no_quote_regex, type_string) is not None or type_string.endswith('?')): # Messages are easier to read if these aren't quoted. We use a # regex to match strings with variable contents. return type_string @@ -309,6 +309,8 @@ def format_bare(self, typ: Type, verbosity: int = 0) -> str: return '' elif isinstance(typ, TypeType): return 'Type[{}]'.format(self.format_bare(typ.item, verbosity)) + elif isinstance(typ, ForwardRef): # may appear in semanal.py + return self.format_bare(typ.link, verbosity) elif isinstance(typ, FunctionLike): func = typ if func.is_type_obj(): @@ -350,6 +352,8 @@ def format_bare(self, typ: Type, verbosity: int = 0) -> str: # function types may result in long and difficult-to-read # error messages. return 'overloaded function' + elif isinstance(typ, UnboundType): + return str(typ) elif typ is None: raise RuntimeError('Type is None') else: diff --git a/mypy/nodes.py b/mypy/nodes.py index 7f7ceb5fb8fe..12bfa0dc986f 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1976,6 +1976,12 @@ class is generic then it will be a type constructor of higher kind. # Is this a newtype type? is_newtype = False + # If during analysis of ClassDef associated with this TypeInfo a syntethic + # type (NamedTuple or TypedDict) was generated, store the corresponding + # TypeInfo here. (This attribute does not need to be serialized, it is only + # needed during the semantic passes.) + replaced = None # type: TypeInfo + FLAGS = [ 'is_abstract', 'is_enum', 'fallback_to_any', 'is_named_tuple', 'is_newtype', 'is_protocol', 'runtime_protocol' diff --git a/mypy/semanal.py b/mypy/semanal.py index d0b301193697..53cecba70176 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -80,7 +80,8 @@ from mypy.types import ( FunctionLike, UnboundType, TypeVarDef, TypeType, TupleType, UnionType, StarType, function_type, TypedDictType, NoneTyp, CallableType, Overloaded, Instance, Type, TypeVarType, AnyType, - TypeTranslator, TypeOfAny + TypeTranslator, TypeOfAny, TypeVisitor, UninhabitedType, ErasedType, DeletedType, + PartialType, ForwardRef ) from mypy.nodes import implicit_module_attrs from mypy.typeanal import ( @@ -941,8 +942,11 @@ def analyze_namedtuple_classdef(self, defn: ClassDef) -> Optional[TypeInfo]: info = self.build_namedtuple_typeinfo( defn.name, items, types, default_items) node.node = info + defn.info.replaced = info defn.info = info defn.analyzed = NamedTupleExpr(info) + defn.analyzed.line = defn.line + defn.analyzed.column = defn.column return info return None @@ -1042,6 +1046,10 @@ def analyze_base_classes(self, defn: ClassDef) -> None: 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 elif isinstance(base, Instance): if base.type.is_newtype: self.fail("Cannot subclass NewType", defn) @@ -1280,8 +1288,11 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> bool: # Building a new TypedDict fields, types, required_keys = self.check_typeddict_classdef(defn) info = self.build_typeddict_typeinfo(defn.name, fields, types, required_keys) + defn.info.replaced = info node.node = info defn.analyzed = TypedDictExpr(info) + defn.analyzed.line = defn.line + defn.analyzed.column = defn.column return True # Extending/merging existing TypedDicts if any(not isinstance(expr, RefExpr) or @@ -1312,8 +1323,11 @@ def analyze_typeddict_classdef(self, defn: ClassDef) -> bool: types.extend(new_types) required_keys.update(new_required_keys) info = self.build_typeddict_typeinfo(defn.name, keys, types, required_keys) + defn.info.replaced = info node.node = info defn.analyzed = TypedDictExpr(info) + defn.analyzed.line = defn.line + defn.analyzed.column = defn.column return True return False @@ -1608,29 +1622,37 @@ def visit_block_maybe(self, b: Block) -> None: def type_analyzer(self, *, tvar_scope: Optional[TypeVarScope] = None, allow_tuple_literal: bool = False, - aliasing: bool = False) -> TypeAnalyser: + aliasing: bool = False, + third_pass: bool = False) -> TypeAnalyser: if tvar_scope is None: tvar_scope = self.tvar_scope - return TypeAnalyser(self.lookup_qualified, + tpan = TypeAnalyser(self.lookup_qualified, self.lookup_fully_qualified, tvar_scope, self.fail, + self.note, self.plugin, self.options, self.is_typeshed_stub_file, aliasing=aliasing, allow_tuple_literal=allow_tuple_literal, - allow_unnormalized=self.is_stub_file) + allow_unnormalized=self.is_stub_file, + third_pass=third_pass) + tpan.in_dynamic_func = bool(self.function_stack and self.function_stack[-1].is_dynamic()) + tpan.global_scope = not self.type and not self.function_stack + return tpan def anal_type(self, t: Type, *, tvar_scope: Optional[TypeVarScope] = None, allow_tuple_literal: bool = False, - aliasing: bool = False) -> Type: + aliasing: bool = False, + third_pass: bool = False) -> Type: if t: a = self.type_analyzer( tvar_scope=tvar_scope, aliasing=aliasing, - allow_tuple_literal=allow_tuple_literal) + allow_tuple_literal=allow_tuple_literal, + third_pass=third_pass) return t.accept(a) else: @@ -1716,15 +1738,20 @@ def analyze_alias(self, rvalue: Expression, qualified type variable names for generic aliases. If 'allow_unnormalized' is True, allow types like builtins.list[T]. """ + dynamic = bool(self.function_stack and self.function_stack[-1].is_dynamic()) + global_scope = not self.type and not self.function_stack res = analyze_type_alias(rvalue, self.lookup_qualified, self.lookup_fully_qualified, self.tvar_scope, self.fail, + self.note, self.plugin, self.options, self.is_typeshed_stub_file, - allow_unnormalized=True) + allow_unnormalized=True, + in_dynamic_func=dynamic, + global_scope=global_scope) if res: alias_tvars = [name for (name, _) in res.accept(TypeVariableQuery(self.lookup_qualified, self.tvar_scope))] @@ -1971,7 +1998,7 @@ def process_newtype_declaration(self, s: AssignmentStmt) -> None: newtype_class_info = self.build_newtype_typeinfo(name, old_type, old_type) else: message = "Argument 2 to NewType(...) must be subclassable (got {})" - self.fail(message.format(old_type), s) + self.fail(message.format(self.msg.format(old_type)), s) return check_for_explicit_any(old_type, self.options, self.is_typeshed_stub_file, self.msg, @@ -2339,7 +2366,12 @@ def basic_new_typeinfo(self, name: str, basetype_or_fallback: Instance) -> TypeI class_def.fullname = self.qualified_name(name) info = TypeInfo(SymbolTable(), class_def, self.cur_mod_id) - info.mro = [info] + basetype_or_fallback.type.mro + class_def.info = info + mro = basetype_or_fallback.type.mro + if mro is None: + # Forward reference, MRO should be recalculated in third pass. + mro = [basetype_or_fallback.type, self.object_type().type] + info.mro = [info] + mro info.bases = [basetype_or_fallback] return info @@ -2353,11 +2385,6 @@ def build_namedtuple_typeinfo(self, name: str, items: List[str], types: List[Typ # Actual signature should return OrderedDict[str, Union[types]] ordereddictype = (self.named_type_or_none('builtins.dict', [strtype, implicit_any]) or self.object_type()) - # 'builtins.tuple' has only one type parameter. - # - # TODO: The corresponding type argument in the fallback instance should be a join of - # all item types, but we can't do joins during this pass of semantic analysis - # and we are using Any as a workaround. fallback = self.named_type('__builtins__.tuple', [implicit_any]) # Note: actual signature should accept an invariant version of Iterable[UnionType[types]]. # but it can't be expressed. 'new' and 'len' should be callable types. @@ -2368,6 +2395,15 @@ def build_namedtuple_typeinfo(self, name: str, items: List[str], types: List[Typ info.is_named_tuple = True info.tuple_type = TupleType(types, fallback) + def patch() -> None: + # Calculate the correct value type for the fallback Mapping. + fallback.args[0] = join.join_type_list(list(info.tuple_type.items)) + + # 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.patches.append(patch) + def add_field(var: Var, is_initialized_in_class: bool = False, is_property: bool = False) -> None: var.info = info @@ -2587,19 +2623,18 @@ def build_typeddict_typeinfo(self, name: str, items: List[str], fallback = (self.named_type_or_none('typing.Mapping', [self.str_type(), self.object_type()]) or self.object_type()) + info = self.basic_new_typeinfo(name, fallback) + info.typeddict_type = TypedDictType(OrderedDict(zip(items, types)), required_keys, + fallback) def patch() -> None: # Calculate the correct value type for the fallback Mapping. - fallback.args[1] = join.join_type_list(types) + fallback.args[1] = join.join_type_list(list(info.typeddict_type.items.values())) # 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.patches.append(patch) - - info = self.basic_new_typeinfo(name, fallback) - info.typeddict_type = TypedDictType(OrderedDict(zip(items, types)), required_keys, - fallback) return info def check_classvar(self, s: AssignmentStmt) -> None: @@ -4044,14 +4079,20 @@ class ThirdPass(TraverserVisitor): straightforward type inference. """ - def __init__(self, modules: Dict[str, MypyFile], errors: Errors) -> None: + def __init__(self, modules: Dict[str, MypyFile], errors: Errors, + sem: SemanticAnalyzer) -> None: self.modules = modules self.errors = errors + self.sem = sem - def visit_file(self, file_node: MypyFile, fnam: str, options: Options) -> None: + def visit_file(self, file_node: MypyFile, fnam: str, options: Options, + patches: List[Callable[[], None]]) -> None: self.errors.set_file(fnam, file_node.fullname()) self.options = options + self.sem.options = options + self.patches = patches self.is_typeshed_file = self.errors.is_typeshed_file(fnam) + self.sem.globals = file_node.names with experiments.strict_optional_set(options.strict_optional): self.accept(file_node) @@ -4081,7 +4122,7 @@ def visit_block(self, b: Block) -> None: def visit_func_def(self, fdef: FuncDef) -> None: self.errors.push_function(fdef.name()) - self.analyze(fdef.type) + self.analyze(fdef.type, fdef) super().visit_func_def(fdef) self.errors.pop_function() @@ -4089,8 +4130,14 @@ def visit_class_def(self, tdef: ClassDef) -> None: # NamedTuple base classes are validated in check_namedtuple_classdef; we don't have to # check them again here. if not tdef.info.is_named_tuple: + types = list(tdef.info.bases) # type: List[Type] + for tvar in tdef.type_vars: + if tvar.upper_bound: + types.append(tvar.upper_bound) + if tvar.values: + types.extend(tvar.values) + self.analyze_types(types, tdef.info) for type in tdef.info.bases: - self.analyze(type) if tdef.info.is_protocol: if not isinstance(type, Instance) or not type.type.is_protocol: if type.type.fullname() != 'builtins.object': @@ -4104,10 +4151,18 @@ def visit_class_def(self, tdef: ClassDef) -> None: if tdef.info.is_protocol: add_protocol_members(tdef.info) if tdef.analyzed is not None: + # Also check synthetic types associated with this ClassDef. + # Currently these are TypedDict, and NamedTuple. if isinstance(tdef.analyzed, TypedDictExpr): - self.analyze(tdef.analyzed.info.typeddict_type) + self.analyze(tdef.analyzed.info.typeddict_type, tdef.analyzed, warn=True) elif isinstance(tdef.analyzed, NamedTupleExpr): - self.analyze(tdef.analyzed.info.tuple_type) + self.analyze(tdef.analyzed.info.tuple_type, tdef.analyzed, warn=True) + for name in tdef.analyzed.info.names: + sym = tdef.analyzed.info.names[name] + if isinstance(sym.node, (FuncDef, Decorator)): + self.accept(sym.node) + if isinstance(sym.node, Var): + self.analyze(sym.node.type, sym.node) super().visit_class_def(tdef) def visit_decorator(self, dec: Decorator) -> None: @@ -4162,20 +4217,60 @@ def visit_decorator(self, dec: Decorator) -> None: dec.var.type = sig def visit_assignment_stmt(self, s: AssignmentStmt) -> None: - self.analyze(s.type) + """Traverse the assignment statement. + + This includes the actual assignment and synthetic types + resulted from this assignment (if any). Currently this includes + NewType, TypedDict, NamedTuple, and TypeVar. + """ + self.analyze(s.type, s) if isinstance(s.rvalue, IndexExpr) and isinstance(s.rvalue.analyzed, TypeAliasExpr): - self.analyze(s.rvalue.analyzed.type) + self.analyze(s.rvalue.analyzed.type, s.rvalue.analyzed, warn=True) if isinstance(s.rvalue, CallExpr): - if isinstance(s.rvalue.analyzed, NewTypeExpr): - self.analyze(s.rvalue.analyzed.old_type) - if isinstance(s.rvalue.analyzed, TypedDictExpr): - self.analyze(s.rvalue.analyzed.info.typeddict_type) - if isinstance(s.rvalue.analyzed, NamedTupleExpr): - self.analyze(s.rvalue.analyzed.info.tuple_type) + analyzed = s.rvalue.analyzed + if isinstance(analyzed, NewTypeExpr): + self.analyze(analyzed.old_type, analyzed) + if analyzed.info and analyzed.info.mro: + analyzed.info.mro = [] # Force recomputation + calculate_class_mro(analyzed.info.defn, self.fail_blocker) + if isinstance(analyzed, TypeVarExpr): + types = [] + if analyzed.upper_bound: + types.append(analyzed.upper_bound) + if analyzed.values: + types.extend(analyzed.values) + self.analyze_types(types, analyzed) + if isinstance(analyzed, TypedDictExpr): + self.analyze(analyzed.info.typeddict_type, analyzed, warn=True) + if isinstance(analyzed, NamedTupleExpr): + self.analyze(analyzed.info.tuple_type, analyzed, warn=True) + for name in analyzed.info.names: + sym = analyzed.info.names[name] + if isinstance(sym.node, (FuncDef, Decorator)): + self.accept(sym.node) + if isinstance(sym.node, Var): + self.analyze(sym.node.type, sym.node) + # We need to pay additional attention to assignments that define a type alias. + # The resulting type is also stored in the 'type_override' attribute of + # the corresponding SymbolTableNode. + if isinstance(s.lvalues[0], RefExpr) and isinstance(s.lvalues[0].node, Var): + self.analyze(s.lvalues[0].node.type, s.lvalues[0].node) + if isinstance(s.lvalues[0], NameExpr): + node = self.sem.lookup(s.lvalues[0].name, s, suppress_errors=True) + if node: + self.analyze(node.type_override, node) super().visit_assignment_stmt(s) + def visit_for_stmt(self, s: ForStmt) -> None: + self.analyze(s.index_type, s) + super().visit_for_stmt(s) + + def visit_with_stmt(self, s: WithStmt) -> None: + self.analyze(s.target_type, s) + super().visit_with_stmt(s) + def visit_cast_expr(self, e: CastExpr) -> None: - self.analyze(e.type) + self.analyze(e.type, e) super().visit_cast_expr(e) def visit_reveal_type_expr(self, e: RevealTypeExpr) -> None: @@ -4183,16 +4278,109 @@ def visit_reveal_type_expr(self, e: RevealTypeExpr) -> None: def visit_type_application(self, e: TypeApplication) -> None: for type in e.types: - self.analyze(type) + self.analyze(type, e) super().visit_type_application(e) # Helpers - def analyze(self, type: Optional[Type]) -> None: + def perform_transform(self, node: Union[Node, SymbolTableNode], + transform: Callable[[Type], Type]) -> None: + """Apply transform to all types associated with node.""" + if isinstance(node, ForStmt): + node.index_type = transform(node.index_type) + self.transform_types_in_lvalue(node.index, transform) + if isinstance(node, WithStmt): + node.target_type = transform(node.target_type) + for n in node.target: + if isinstance(n, NameExpr) and isinstance(n.node, Var) and n.node.type: + n.node.type = transform(n.node.type) + if isinstance(node, (FuncDef, CastExpr, AssignmentStmt, TypeAliasExpr, Var)): + node.type = transform(node.type) + if isinstance(node, NewTypeExpr): + node.old_type = transform(node.old_type) + if isinstance(node, TypeVarExpr): + if node.upper_bound: + node.upper_bound = transform(node.upper_bound) + if node.values: + node.values = [transform(v) for v in node.values] + if isinstance(node, TypedDictExpr): + node.info.typeddict_type = cast(TypedDictType, + transform(node.info.typeddict_type)) + if isinstance(node, NamedTupleExpr): + node.info.tuple_type = cast(TupleType, + transform(node.info.tuple_type)) + if isinstance(node, TypeApplication): + node.types = [transform(t) for t in node.types] + if isinstance(node, SymbolTableNode): + node.type_override = transform(node.type_override) + if isinstance(node, TypeInfo): + for tvar in node.defn.type_vars: + if tvar.upper_bound: + tvar.upper_bound = transform(tvar.upper_bound) + if tvar.values: + tvar.values = [transform(v) for v in tvar.values] + new_bases = [] + for base in node.bases: + new_base = transform(base) + if isinstance(new_base, Instance): + new_bases.append(new_base) + else: + # Don't fix the NamedTuple bases, they are Instance's intentionally. + # Patch the 'args' just in case, although generic tuple type are + # not supported yet. + alt_base = Instance(base.type, [transform(a) for a in base.args]) + new_bases.append(alt_base) + node.bases = new_bases + + def transform_types_in_lvalue(self, lvalue: Lvalue, + transform: Callable[[Type], Type]) -> None: + if isinstance(lvalue, RefExpr): + if isinstance(lvalue.node, Var): + var = lvalue.node + var.type = transform(var.type) + elif isinstance(lvalue, TupleExpr): + for item in lvalue.items: + self.transform_types_in_lvalue(item, transform) + + def analyze(self, type: Optional[Type], node: Union[Node, SymbolTableNode], + warn: bool = False) -> None: + # Recursive type warnings are only emitted on type definition 'node's, marked by 'warn' + # Flags appeared during analysis of 'type' are collected in this dict. + indicator = {} # type: Dict[str, bool] if type: - analyzer = TypeAnalyserPass3(self.fail, self.options, self.is_typeshed_file) + analyzer = self.make_type_analyzer(indicator) type.accept(analyzer) self.check_for_omitted_generics(type) + if indicator.get('forward') or indicator.get('synthetic'): + def patch() -> None: + self.perform_transform(node, + lambda tp: tp.accept(ForwardReferenceResolver(self.fail, + node, warn))) + self.patches.append(patch) + + def analyze_types(self, types: List[Type], node: Node) -> None: + # Similar to above but for nodes with multiple types. + indicator = {} # type: Dict[str, bool] + for type in types: + analyzer = self.make_type_analyzer(indicator) + type.accept(analyzer) + self.check_for_omitted_generics(type) + if indicator.get('forward') or indicator.get('synthetic'): + def patch() -> None: + self.perform_transform(node, + lambda tp: tp.accept(ForwardReferenceResolver(self.fail, + node, warn=False))) + self.patches.append(patch) + + def make_type_analyzer(self, indicator: Dict[str, bool]) -> TypeAnalyserPass3: + return TypeAnalyserPass3(self.sem.lookup_qualified, + self.sem.lookup_fully_qualified, + self.fail, + self.sem.note, + self.sem.plugin, + self.options, + self.is_typeshed_file, + indicator) def check_for_omitted_generics(self, typ: Type) -> None: if 'generics' not in self.options.disallow_any or self.is_typeshed_file: @@ -4618,3 +4806,135 @@ def visit_any(self, t: AnyType) -> Type: if t.type_of_any == TypeOfAny.explicit: return t.copy_modified(TypeOfAny.special_form) return t + + +class ForwardReferenceResolver(TypeTranslator): + """Visitor to replace previously detected forward reference to synthetic types. + + This is similar to TypeTranslator but tracks visited nodes to avoid + infinite recursion on potentially circular (self- or mutually-referential) types. + This visitor: + * Fixes forward references by unwrapping the linked type. + * Generates errors for unsupported type recursion and breaks recursion by resolving + recursive back references to Any types. + * Replaces instance types generated from unanalyzed NamedTuple and TypedDict class syntax + found in first pass with analyzed TupleType and TypedDictType. + """ + def __init__(self, fail: Callable[[str, Context], None], + start: Union[Node, SymbolTableNode], warn: bool) -> None: + self.seen = [] # type: List[Type] + self.fail = fail + self.start = start + self.warn = warn + + def check_recursion(self, t: Type) -> bool: + if any(t is s for s in self.seen): + if self.warn: + assert isinstance(self.start, Node), "Internal error: invalid error context" + self.fail('Recursive types not fully supported yet,' + ' nested types replaced with "Any"', self.start) + return True + self.seen.append(t) + return False + + def visit_forwardref_type(self, t: ForwardRef) -> Type: + """This visitor method tracks situations like this: + + x: A # This type is not yet known and therefore wrapped in ForwardRef, + # its content is updated in ThirdPass, now we need to unwrap this type. + A = NewType('A', int) + """ + return t.link.accept(self) + + def visit_instance(self, t: Instance, from_fallback: bool = False) -> Type: + """This visitor method tracks situations like this: + + x: A # When analyzing this type we will get an Instance from FirstPass. + # Now we need to update this to actual analyzed TupleType. + class A(NamedTuple): + attr: str + + If from_fallback is True, then we always return an Instance type. This is needed + since TupleType and TypedDictType fallbacks are always instances. + """ + info = t.type + # Special case, analyzed bases transformed the type into TupleType. + if info.tuple_type and not from_fallback: + items = [it.accept(self) for it in info.tuple_type.items] + info.tuple_type.items = items + return TupleType(items, Instance(info, [])) + # Update forward Instances to corresponding analyzed NamedTuples. + if info.replaced and info.replaced.tuple_type: + tp = info.replaced.tuple_type + if self.check_recursion(tp): + # The key idea is that when we recursively return to a type already traversed, + # then we break the cycle and put AnyType as a leaf. + return AnyType(TypeOfAny.from_error) + return tp.copy_modified(fallback=Instance(info.replaced, [])).accept(self) + # Same as above but for TypedDicts. + if info.replaced and info.replaced.typeddict_type: + td = info.replaced.typeddict_type + if self.check_recursion(td): + # We also break the cycles for TypedDicts as explained above for NamedTuples. + return AnyType(TypeOfAny.from_error) + return td.copy_modified(fallback=Instance(info.replaced, [])).accept(self) + if self.check_recursion(t): + # We also need to break a potential cycle with normal (non-synthetic) instance types. + return Instance(t.type, [AnyType(TypeOfAny.from_error)] * len(t.type.defn.type_vars)) + return super().visit_instance(t) + + def visit_type_var(self, t: TypeVarType) -> Type: + if self.check_recursion(t): + return AnyType(TypeOfAny.from_error) + if t.upper_bound: + t.upper_bound = t.upper_bound.accept(self) + if t.values: + t.values = [v.accept(self) for v in t.values] + return t + + def visit_callable_type(self, t: CallableType) -> Type: + if self.check_recursion(t): + return AnyType(TypeOfAny.from_error) + arg_types = [tp.accept(self) for tp in t.arg_types] + ret_type = t.ret_type.accept(self) + variables = t.variables.copy() + for v in variables: + if v.upper_bound: + v.upper_bound = v.upper_bound.accept(self) + if v.values: + v.values = [val.accept(self) for val in v.values] + return t.copy_modified(arg_types=arg_types, ret_type=ret_type, variables=variables) + + def visit_overloaded(self, t: Overloaded) -> Type: + if self.check_recursion(t): + return AnyType(TypeOfAny.from_error) + return super().visit_overloaded(t) + + 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) + assert isinstance(fallback, Instance) + return TupleType(items, fallback, t.line, t.column) + + def visit_typeddict_type(self, t: TypedDictType) -> Type: + if self.check_recursion(t): + return AnyType(TypeOfAny.from_error) + items = OrderedDict([ + (item_name, item_type.accept(self)) + for (item_name, item_type) in t.items.items() + ]) + fallback = self.visit_instance(t.fallback, from_fallback=True) + assert isinstance(fallback, Instance) + return TypedDictType(items, t.required_keys, fallback, t.line, t.column) + + def visit_union_type(self, t: UnionType) -> Type: + if self.check_recursion(t): + return AnyType(TypeOfAny.from_error) + return super().visit_union_type(t) + + def visit_type_type(self, t: TypeType) -> Type: + if self.check_recursion(t): + return AnyType(TypeOfAny.from_error) + return super().visit_type_type(t) diff --git a/mypy/server/deps.py b/mypy/server/deps.py index c271fb7f51a2..0402a511a7ab 100644 --- a/mypy/server/deps.py +++ b/mypy/server/deps.py @@ -11,7 +11,7 @@ from mypy.types import ( Type, Instance, AnyType, NoneTyp, TypeVisitor, CallableType, DeletedType, PartialType, TupleType, TypeType, TypeVarType, TypedDictType, UnboundType, UninhabitedType, UnionType, - FunctionLike + FunctionLike, ForwardRef ) from mypy.server.trigger import make_trigger @@ -212,6 +212,9 @@ def visit_type_type(self, typ: TypeType) -> List[str]: # TODO: replace with actual implementation return [] + def visit_forwardref_type(self, typ: ForwardRef) -> List[str]: + return get_type_dependencies(typ.link) + def visit_type_var(self, typ: TypeVarType) -> List[str]: # TODO: replace with actual implementation return [] diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 1a7f66d0ae22..3119c19a05e3 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -1,7 +1,7 @@ """Semantic analysis of types""" from collections import OrderedDict -from typing import Callable, List, Optional, Set, Tuple, Iterator, TypeVar, Iterable +from typing import Callable, List, Optional, Set, Tuple, Iterator, TypeVar, Iterable, Dict from itertools import chain from contextlib import contextmanager @@ -14,13 +14,14 @@ Type, UnboundType, TypeVarType, TupleType, TypedDictType, UnionType, Instance, AnyType, CallableType, NoneTyp, DeletedType, TypeList, TypeVarDef, TypeVisitor, SyntheticTypeVisitor, StarType, PartialType, EllipsisType, UninhabitedType, TypeType, get_typ_args, set_typ_args, - CallableArgument, get_type_vars, TypeQuery, union_items, TypeOfAny + CallableArgument, get_type_vars, TypeQuery, union_items, TypeOfAny, ForwardRef ) from mypy.nodes import ( TVAR, TYPE_ALIAS, UNBOUND_IMPORTED, TypeInfo, Context, SymbolTableNode, Var, Expression, IndexExpr, RefExpr, nongen_builtins, check_arg_names, check_arg_kinds, ARG_POS, ARG_NAMED, - ARG_OPT, ARG_NAMED_OPT, ARG_STAR, ARG_STAR2, TypeVarExpr, CallExpr, NameExpr + ARG_OPT, ARG_NAMED_OPT, ARG_STAR, ARG_STAR2, TypeVarExpr, FuncDef, CallExpr, NameExpr, + Decorator ) from mypy.tvar_scope import TypeVarScope from mypy.sametypes import is_same_type @@ -56,10 +57,13 @@ def analyze_type_alias(node: Expression, lookup_fqn_func: Callable[[str], SymbolTableNode], tvar_scope: TypeVarScope, fail_func: Callable[[str, Context], None], + note_func: Callable[[str, Context], None], plugin: Plugin, options: Options, is_typeshed_stub: bool, - allow_unnormalized: bool = False) -> Optional[Type]: + allow_unnormalized: bool = False, + in_dynamic_func: bool = False, + global_scope: bool = True) -> Optional[Type]: """Return type if node is valid as a type alias rvalue. Return None otherwise. 'node' must have been semantically analyzed. @@ -111,8 +115,11 @@ def analyze_type_alias(node: Expression, except TypeTranslationError: fail_func('Invalid type alias', node) return None - analyzer = TypeAnalyser(lookup_func, lookup_fqn_func, tvar_scope, fail_func, plugin, options, - is_typeshed_stub, aliasing=True, allow_unnormalized=allow_unnormalized) + analyzer = TypeAnalyser(lookup_func, lookup_fqn_func, tvar_scope, fail_func, note_func, + plugin, options, is_typeshed_stub, aliasing=True, + allow_unnormalized=allow_unnormalized) + analyzer.in_dynamic_func = in_dynamic_func + analyzer.global_scope = global_scope return type.accept(analyzer) @@ -130,20 +137,28 @@ class TypeAnalyser(SyntheticTypeVisitor[Type], AnalyzerPluginInterface): Converts unbound types into bound types. """ + # Is this called from an untyped function definition? + in_dynamic_func = False # type: bool + # Is this called from global scope? + global_scope = True # type: bool + def __init__(self, lookup_func: Callable[[str, Context], SymbolTableNode], lookup_fqn_func: Callable[[str], SymbolTableNode], - tvar_scope: TypeVarScope, + tvar_scope: Optional[TypeVarScope], fail_func: Callable[[str, Context], None], + note_func: Callable[[str, Context], None], plugin: Plugin, options: Options, is_typeshed_stub: bool, *, aliasing: bool = False, allow_tuple_literal: bool = False, - allow_unnormalized: bool = False) -> None: + allow_unnormalized: bool = False, + third_pass: bool = False) -> None: self.lookup = lookup_func self.lookup_fqn_func = lookup_fqn_func self.fail_func = fail_func + self.note_func = note_func self.tvar_scope = tvar_scope self.aliasing = aliasing self.allow_tuple_literal = allow_tuple_literal @@ -153,6 +168,7 @@ def __init__(self, self.plugin = plugin self.options = options self.is_typeshed_stub = is_typeshed_stub + self.third_pass = third_pass def visit_unbound_type(self, t: UnboundType) -> Type: if t.optional: @@ -160,7 +176,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: # We don't need to worry about double-wrapping Optionals or # wrapping Anys: Union simplification will take care of that. return make_optional_type(self.visit_unbound_type(t)) - sym = self.lookup(t.name, t) + sym = self.lookup(t.name, t, suppress_errors=self.third_pass) # type: ignore if sym is not None: if sym.node is None: # UNBOUND_IMPORTED can happen if an unknown name was imported. @@ -174,7 +190,10 @@ def visit_unbound_type(self, t: UnboundType) -> Type: if (fullname in nongen_builtins and t.args and not sym.normalized and not self.allow_unnormalized): self.fail(no_subscript_builtin_alias(fullname), t) - tvar_def = self.tvar_scope.get_binding(sym) + if self.tvar_scope: + tvar_def = self.tvar_scope.get_binding(sym) + else: + tvar_def = None if sym.kind == TVAR and tvar_def is not None: if len(t.args) > 0: self.fail('Type variable "{}" used with arguments'.format( @@ -268,8 +287,18 @@ def visit_unbound_type(self, t: UnboundType) -> Type: return AnyType(TypeOfAny.from_unimported_type) # Allow unbound type variables when defining an alias if not (self.aliasing and sym.kind == TVAR and - self.tvar_scope.get_binding(sym) is None): + (not self.tvar_scope or self.tvar_scope.get_binding(sym) is None)): + if (not self.third_pass and not self.in_dynamic_func and + not (isinstance(sym.node, (FuncDef, Decorator)) or + isinstance(sym.node, Var) and sym.node.is_ready) and + not (sym.kind == TVAR and tvar_def is None)): + if t.args and not self.global_scope: + self.fail('Unsupported forward reference to "{}"'.format(t.name), t) + return AnyType(TypeOfAny.from_error) + return ForwardRef(t) self.fail('Invalid type "{}"'.format(name), t) + if self.third_pass and sym.kind == TVAR: + self.note_func("Forward references to type variables are prohibited", t) return t info = sym.node # type: TypeInfo if len(t.args) > 0 and info.fullname() == 'builtins.tuple': @@ -304,6 +333,9 @@ def visit_unbound_type(self, t: UnboundType) -> Type: fallback=instance) return instance else: + if self.third_pass: + self.fail('Invalid type "{}"'.format(t.name), t) + return AnyType(TypeOfAny.from_error) return AnyType(TypeOfAny.special_form) def visit_any(self, t: AnyType) -> Type: @@ -387,6 +419,9 @@ def visit_ellipsis_type(self, t: EllipsisType) -> Type: def visit_type_type(self, t: TypeType) -> Type: return TypeType.make_normalized(self.anal_type(t.item), line=t.line) + def visit_forwardref_type(self, t: ForwardRef) -> Type: + return t + def analyze_callable_type(self, t: UnboundType) -> Type: fallback = self.named_type('builtins.function') if len(t.args) == 0: @@ -476,13 +511,18 @@ def fail(self, msg: str, ctx: Context) -> None: @contextmanager def tvar_scope_frame(self) -> Iterator[None]: old_scope = self.tvar_scope - self.tvar_scope = self.tvar_scope.method_frame() + if self.tvar_scope: + self.tvar_scope = self.tvar_scope.method_frame() + else: + assert self.third_pass, "Internal error: type variable scope not given" yield self.tvar_scope = old_scope def infer_type_variables(self, type: CallableType) -> List[Tuple[str, TypeVarExpr]]: """Return list of unique type variables referred to in a callable.""" + if not self.tvar_scope: + return [] # We are in third pass, nothing new here names = [] # type: List[str] tvars = [] # type: List[TypeVarExpr] for arg in type.arg_types: @@ -504,6 +544,8 @@ def infer_type_variables(self, def bind_function_type_variables(self, fun_type: CallableType, defn: Context) -> List[TypeVarDef]: """Find the type variables of the function type and bind them in our tvar_scope""" + if not self.tvar_scope: + return [] # We are in third pass, nothing new here if fun_type.variables: for var in fun_type.variables: var_expr = self.lookup(var.name, var).node @@ -526,7 +568,8 @@ def bind_function_type_variables(self, return defs def is_defined_type_var(self, tvar: str, context: Context) -> bool: - return self.tvar_scope.get_binding(self.lookup(tvar, context)) is not None + return (self.tvar_scope is not None and + self.tvar_scope.get_binding(self.lookup(tvar, context)) is not None) def anal_array(self, a: List[Type], nested: bool = True) -> List[Type]: res = [] # type: List[Type] @@ -588,15 +631,27 @@ class TypeAnalyserPass3(TypeVisitor[None]): """ def __init__(self, + lookup_func: Callable[[str, Context], SymbolTableNode], + lookup_fqn_func: Callable[[str], SymbolTableNode], fail_func: Callable[[str, Context], None], + note_func: Callable[[str, Context], None], + plugin: Plugin, options: Options, - is_typeshed_stub: bool) -> None: + is_typeshed_stub: bool, + indicator: Dict[str, bool]) -> None: + self.lookup_func = lookup_func + self.lookup_fqn_func = lookup_fqn_func self.fail = fail_func + self.note_func = note_func self.options = options + self.plugin = plugin self.is_typeshed_stub = is_typeshed_stub + self.indicator = indicator def visit_instance(self, t: Instance) -> None: info = t.type + if info.replaced or info.tuple_type: + self.indicator['synthetic'] = True # Check type argument count. if len(t.args) != len(info.type_vars): if len(t.args) == 0: @@ -635,6 +690,7 @@ def visit_instance(self, t: Instance) -> None: t.invalid = True elif info.defn.type_vars: # Check type argument values. + # TODO: Calling is_subtype and is_same_types in semantic analysis is a bad idea for (i, arg), tvar in zip(enumerate(t.args), info.defn.type_vars): if tvar.values: if isinstance(arg, TypeVarType): @@ -647,10 +703,13 @@ def visit_instance(self, t: Instance) -> None: else: arg_values = [arg] self.check_type_var_values(info, arg_values, tvar.name, tvar.values, i + 1, t) - if not is_subtype(arg, tvar.upper_bound): + # TODO: These hacks will be not necessary when this will be moved to later stage. + arg = self.update_type(arg) + bound = self.update_type(tvar.upper_bound) + if not is_subtype(arg, bound): self.fail('Type argument "{}" of "{}" must be ' 'a subtype of "{}"'.format( - arg, info.name(), tvar.upper_bound), t) + arg, info.name(), bound), t) for arg in t.args: arg.accept(self) if info.is_newtype: @@ -660,8 +719,9 @@ def visit_instance(self, t: Instance) -> None: def check_type_var_values(self, type: TypeInfo, actuals: List[Type], arg_name: str, valids: List[Type], arg_number: int, context: Context) -> None: for actual in actuals: + actual = self.update_type(actual) if (not isinstance(actual, AnyType) and - not any(is_same_type(actual, value) for value in valids)): + not any(is_same_type(actual, self.update_type(value)) for value in valids)): if len(actuals) > 1 or not isinstance(actual, Instance): self.fail('Invalid type argument value for "{}"'.format( type.name()), context) @@ -671,6 +731,19 @@ def check_type_var_values(self, type: TypeInfo, actuals: List[Type], arg_name: s self.fail(messages.INCOMPATIBLE_TYPEVAR_VALUE.format( arg_name, class_name, actual_type_name), context) + def update_type(self, tp: Type) -> Type: + # This helper is only needed while is_subtype and is_same_type are + # called in third pass. This can be removed when TODO in visit_instance is fixed. + if isinstance(tp, ForwardRef): + tp = tp.link + if isinstance(tp, Instance) and tp.type.replaced: + replaced = tp.type.replaced + if replaced.tuple_type: + tp = replaced.tuple_type + if replaced.typeddict_type: + tp = replaced.typeddict_type + return tp + def visit_callable_type(self, t: CallableType) -> None: t.ret_type.accept(self) for arg_type in t.arg_types: @@ -712,13 +785,34 @@ def visit_type_list(self, t: TypeList) -> None: self.fail('Invalid type', t) def visit_type_var(self, t: TypeVarType) -> None: - pass + if t.upper_bound: + t.upper_bound.accept(self) + if t.values: + for v in t.values: + v.accept(self) def visit_partial_type(self, t: PartialType) -> None: pass def visit_type_type(self, t: TypeType) -> None: - pass + t.item.accept(self) + + def visit_forwardref_type(self, t: ForwardRef) -> None: + self.indicator['forward'] = True + if isinstance(t.link, UnboundType): + t.link = self.anal_type(t.link) + + def anal_type(self, tp: UnboundType) -> Type: + tpan = TypeAnalyser(self.lookup_func, + self.lookup_fqn_func, + None, + self.fail, + self.note_func, + self.plugin, + self.options, + self.is_typeshed_stub, + third_pass=True) + return tp.accept(tpan) TypeVarList = List[Tuple[str, TypeVarExpr]] diff --git a/mypy/types.py b/mypy/types.py index a53085a66a36..dcc845922419 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1375,6 +1375,41 @@ def deserialize(cls, data: JsonDict) -> Type: return TypeType.make_normalized(deserialize_type(data['item'])) +class ForwardRef(Type): + """Class to wrap forward references to other types. + + This is used when a forward reference to an (unanalyzed) synthetic type is found, + for example: + + x: A + A = TypedDict('A', {'x': int}) + + To avoid false positives and crashes in such situations, we first wrap the first + occurrence of 'A' in ForwardRef. Then, the wrapped UnboundType is updated in the third + pass of semantic analysis and ultimately fixed in the patches after the third pass. + So that ForwardRefs are temporary and will be completely replaced with the linked types + or Any (to avoid cyclic references) before the type checking stage. + """ + link = None # type: Type # The wrapped type + + def __init__(self, link: Type) -> None: + self.link = link + + def accept(self, visitor: 'TypeVisitor[T]') -> T: + return visitor.visit_forwardref_type(self) + + def serialize(self): + if isinstance(self.link, UnboundType): + name = self.link.name + if isinstance(self.link, Instance): + name = self.link.type.name() + else: + name = self.link.__class__.__name__ + # We should never get here since all forward references should be resolved + # and removed during semantic analysis. + assert False, "Internal error: Unresolved forward reference to {}".format(name) + + # # Visitor-related classes # @@ -1450,6 +1485,9 @@ def visit_partial_type(self, t: PartialType) -> T: def visit_type_type(self, t: TypeType) -> T: pass + def visit_forwardref_type(self, t: ForwardRef) -> T: + raise RuntimeError('Internal error: unresolved forward reference') + class SyntheticTypeVisitor(TypeVisitor[T]): """A TypeVisitor that also knows how to visit synthetic AST constructs. @@ -1552,6 +1590,9 @@ def visit_overloaded(self, t: Overloaded) -> Type: def visit_type_type(self, t: TypeType) -> Type: return TypeType.make_normalized(t.item.accept(self), line=t.line, column=t.column) + def visit_forwardref_type(self, t: ForwardRef) -> Type: + return t + class TypeStrVisitor(SyntheticTypeVisitor[str]): """Visitor for pretty-printing types into strings. @@ -1707,6 +1748,9 @@ def visit_ellipsis_type(self, t: EllipsisType) -> str: def visit_type_type(self, t: TypeType) -> str: return 'Type[{}]'.format(t.item.accept(self)) + def visit_forwardref_type(self, t: ForwardRef) -> str: + return '~{}'.format(t.link.accept(self)) + def list_str(self, a: List[Type]) -> str: """Convert items of an array to strings (pretty-print types) and join the results with commas. @@ -1786,6 +1830,9 @@ def visit_overloaded(self, t: Overloaded) -> T: def visit_type_type(self, t: TypeType) -> T: return t.item.accept(self) + def visit_forwardref_type(self, t: ForwardRef) -> T: + return t.link.accept(self) + def visit_ellipsis_type(self, t: EllipsisType) -> T: return self.strategy([]) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 2ff0b8d84d78..7243de98420b 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -3515,9 +3515,256 @@ D = TypeVar('D') def mkdict(dict_type: Type[D]) -> D: ... reveal_type(mkdict(ExampleDict)) # E: Revealed type is '__main__.ExampleDict*[Any, Any]' +[case testTupleForwardBase] +from m import a +a[0]() # E: "int" not callable + +[file m.py] +from typing import Tuple +a = None # type: A +class A(Tuple[int, str]): pass +[builtins fixtures/tuple.pyi] + -- Synthetic types crashes -- ----------------------- +[case testCrashOnSelfRecursiveNamedTupleVar] +from typing import NamedTuple + +N = NamedTuple('N', [('x', N)]) # E: Recursive types not fully supported yet, nested types replaced with "Any" +n: N +[out] + +[case testCrashOnSelfRecursiveTypedDictVar] +from mypy_extensions import TypedDict + +A = TypedDict('A', {'a': 'A'}) # type: ignore +a: A +[builtins fixtures/isinstancelist.pyi] +[out] + +[case testCrashInJoinOfSelfRecursiveNamedTuples] +from typing import NamedTuple + +class N(NamedTuple): # type: ignore + x: N +class M(NamedTuple): # type: ignore + x: M + +n: N +m: M +lst = [n, m] +[builtins fixtures/isinstancelist.pyi] + +[case testCorrectJoinOfSelfRecursiveTypedDicts] +from mypy_extensions import TypedDict + +class N(TypedDict): + x: N +class M(TypedDict): + x: M + +n: N +m: M +lst = [n, m] +reveal_type(lst[0]['x']) # E: Revealed type is 'TypedDict('__main__.N', {'x': Any})' +[builtins fixtures/isinstancelist.pyi] +[out] +main:3: error: Recursive types not fully supported yet, nested types replaced with "Any" +main:5: error: Recursive types not fully supported yet, nested types replaced with "Any" + +[case testCrashInForwardRefToNamedTupleWithIsinstance] +from typing import Dict, NamedTuple + +NameDict = Dict[str, 'NameInfo'] +class NameInfo(NamedTuple): + ast: bool + +def parse_ast(name_dict: NameDict) -> None: + if isinstance(name_dict[''], int): + pass + reveal_type(name_dict['test']) # E: Revealed type is 'Tuple[builtins.bool, fallback=__main__.NameInfo]' +[builtins fixtures/isinstancelist.pyi] +[out] + +[case testCrashInForwardRefToTypedDictWithIsinstance] +from mypy_extensions import TypedDict +from typing import Dict + +NameDict = Dict[str, 'NameInfo'] +class NameInfo(TypedDict): + ast: bool + +def parse_ast(name_dict: NameDict) -> None: + if isinstance(name_dict[''], int): + pass + reveal_type(name_dict['']['ast']) # E: Revealed type is 'builtins.bool' +[builtins fixtures/isinstancelist.pyi] +[out] + +[case testCorrectIsinstanceInForwardRefToNewType] +from typing import Dict, NewType + +NameDict = Dict[str, 'NameInfo'] +class Base: + ast: bool +NameInfo = NewType('NameInfo', Base) + +def parse_ast(name_dict: NameDict) -> None: + if isinstance(name_dict[''], int): + pass + x = name_dict[''] + reveal_type(x) # E: Revealed type is '__main__.NameInfo*' + x = NameInfo(Base()) # OK + x = Base() # E: Incompatible types in assignment (expression has type "Base", variable has type "NameInfo") +[builtins fixtures/isinstancelist.pyi] +[out] + +[case testCorrectAttributeInForwardRefToNamedTuple] +from typing import NamedTuple +proc: Process +reveal_type(proc.state) # E: Revealed type is 'builtins.int' + +def get_state(proc: 'Process') -> int: + return proc.state +class Process(NamedTuple): + state: int +[out] + +[case testCorrectItemTypeInForwardRefToTypedDict] +from mypy_extensions import TypedDict +proc: Process +reveal_type(proc['state']) # E: Revealed type is 'builtins.int' + +def get_state(proc: 'Process') -> int: + return proc['state'] +class Process(TypedDict): + state: int +[builtins fixtures/isinstancelist.pyi] +[out] + +[case testCorrectDoubleForwardNamedTuple] +from typing import NamedTuple + +x: A +class A(NamedTuple): + one: 'B' + other: int +class B(NamedTuple): + attr: str +y: A +y = x +reveal_type(x.one.attr) # E: Revealed type is 'builtins.str' +[out] + +[case testCrashOnDoubleForwardTypedDict] +from mypy_extensions import TypedDict + +x: A +class A(TypedDict): + one: 'B' + other: int +class B(TypedDict): + attr: str + +reveal_type(x['one']['attr']) # E: Revealed type is 'builtins.str' +[builtins fixtures/isinstancelist.pyi] +[out] + +[case testCrashOnForwardUnionOfNamedTuples] +from typing import Union, NamedTuple + +Node = Union['Foo', 'Bar'] +class Foo(NamedTuple): + x: int +class Bar(NamedTuple): + x: int + +def foo(node: Node) -> int: + x = node + reveal_type(node) # E: Revealed type is 'Union[Tuple[builtins.int, fallback=__main__.Foo], Tuple[builtins.int, fallback=__main__.Bar]]' + return x.x +[out] + +[case testCrashOnForwardUnionOfTypedDicts] +from mypy_extensions import TypedDict +from typing import Union + +NodeType = Union['Foo', 'Bar'] +class Foo(TypedDict): + x: int +class Bar(TypedDict): + x: int + +def foo(node: NodeType) -> int: + x = node + return x['x'] +[builtins fixtures/isinstancelist.pyi] +[out] + +[case testSupportForwardUnionOfNewTypes] +from typing import Union, NewType +x: Node +reveal_type(x.x) # E: Revealed type is 'builtins.int' + +class A: + x: int +class B: + x: int + +Node = Union['Foo', 'Bar'] +Foo = NewType('Foo', A) +Bar = NewType('Bar', B) + +def foo(node: Node) -> Node: + x = node + return Foo(A()) +[out] + +[case testForwardReferencesInNewTypeMRORecomputed] +from typing import NewType +x: Foo +Foo = NewType('Foo', B) +class A: + x: int +class B(A): + pass + +reveal_type(x.x) # E: Revealed type is 'builtins.int' +[out] + +[case testCrashOnComplexNamedTupleUnionProperty] +from typing import NamedTuple, Union + +x: AOrB +AOrB = Union['A', 'B'] +class A(NamedTuple): + x: int + +class B(object): + def __init__(self, a: AOrB) -> None: + self.a = a + @property + def x(self) -> int: + return self.a.x + +reveal_type(x.x) # E: Revealed type is 'builtins.int' +[builtins fixtures/property.pyi] +[out] + +[case testCorrectIsinstanceWithForwardUnion] +from typing import Union, NamedTuple + +ForwardUnion = Union['TP', int] +class TP(NamedTuple('TP', [('x', int)])): pass + +def f(x: ForwardUnion) -> None: + reveal_type(x) # E: Revealed type is 'Union[Tuple[builtins.int, fallback=__main__.TP], builtins.int]' + if isinstance(x, TP): + reveal_type(x) # E: Revealed type is 'Tuple[builtins.int, fallback=__main__.TP]' +[builtins fixtures/isinstance.pyi] +[out] + [case testCrashInvalidArgsSyntheticClassSyntax] from typing import List, NamedTuple from mypy_extensions import TypedDict diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index d6e7a65abfc4..30984ad07c46 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -360,6 +360,24 @@ x = y [out] main:8: error: Incompatible types in assignment (expression has type "__main__.B.E", variable has type "__main__.A.E") +[case testEnumWorkWithForward] +from enum import Enum +a: E = E.x +class E(Enum): + x = 1 + y = 2 +[out] + +[case testEnumWorkWithForward2] +from enum import Enum +b: F +F = Enum('F', {'x': 1, 'y': 2}) + +def fn(x: F) -> None: + pass +fn(b) +[out] + [case testFunctionalEnum_python2] from enum import Enum Eu = Enum(u'Eu', u'a b') diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index d5e329425b14..37c2bbebc485 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -668,7 +668,7 @@ main:5: error: Constraint 2 becomes "List[Any]" due to an unfollowed import from typing import NewType, List from missing import Unchecked -Baz = NewType('Baz', Unchecked) # E: Argument 2 to NewType(...) must be subclassable (got Any) +Baz = NewType('Baz', Unchecked) # E: Argument 2 to NewType(...) must be subclassable (got "Any") Bar = NewType('Bar', List[Unchecked]) # E: Argument 2 to NewType(...) becomes "List[Any]" due to an unfollowed import [builtins fixtures/list.pyi] diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 490afac10fa3..1d3ee9a37436 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -2952,6 +2952,201 @@ def foo(a: int, b: int) -> str: [out2] tmp/b.py:2: error: Incompatible return value type (got "int", expected "str") +[case testForwardNamedTupleToUnionWithOtherNamedTUple] +from typing import NamedTuple, Union + +class Person(NamedTuple): + name: Union[str, "Pair"] + +class Pair(NamedTuple): + first: str + last: str + +Person(name=Pair(first="John", last="Doe")) +[out] + +-- Some crazy selef-referential named tuples, types dicts, and aliases +-- to be sure that everything can be _serialized_ (i.e. ForwardRef's are removed). +-- For this reason errors are silenced (tests with # type: ignore have equivalents in other files) + +[case testForwardTypeAliasInBase1] +from typing import List +class C(List['A']): + pass + +A = List[int] +x: int = C()[0][0] +[builtins fixtures/list.pyi] +[out] + +[case testForwardTypeAliasInBase2] +from typing import List, Generic, TypeVar, NamedTuple +T = TypeVar('T') + +class C(A, B): #type: ignore + pass +class G(Generic[T]): pass +A = G[C] +class B(NamedTuple): + x: int + +C().x +C()[0] +[builtins fixtures/list.pyi] +[out] + +[case testSerializeRecursiveAliases1] +from typing import Type, Callable, Union + +A = Union[A, int] # type: ignore +B = Callable[[B], int] # type: ignore +C = Type[C] # type: ignore +[out] + +[case testSerializeRecursiveAliases2] +from typing import Type, Callable, Union + +A = Union[B, int] # type: ignore +B = Callable[[C], int] # type: ignore +C = Type[A] # type: ignore +[out] + +[case testSerializeRecursiveAliases3] +from typing import Type, Callable, Union, NamedTuple + +A = Union[B, int] # type: ignore +B = Callable[[C], int] # type: ignore +class C(NamedTuple): # type: ignore + x: A +[out] + +[case testGenericTypeAliasesForwardAnyIncremental1] +from typing import TypeVar, Generic +T = TypeVar('T') +S = TypeVar('S') +IntNode = Node[int, S] +AnyNode = Node[S, T] + +class Node(Generic[T, S]): + def __init__(self, x: T, y: S) -> None: + self.x = x + self.y = y + +def output() -> IntNode[str]: + return Node(1, 'x') +x = output() # type: IntNode + +y = None # type: IntNode +y.x = 1 +y.y = 1 +y.y = 'x' + +z = Node(1, 'x') # type: AnyNode +[out] + +[case testGenericTypeAliasesForwardAnyIncremental2] +from typing import TypeVar, Generic +T = TypeVar('T') +S = TypeVar('S') + +class Node(Generic[T, S]): + def __init__(self, x: T, y: S) -> None: + self.x = x + self.y = y + +def output() -> IntNode[str]: + return Node(1, 'x') +x = output() # type: IntNode + +y = None # type: IntNode +y.x = 1 +y.y = 1 +y.y = 'x' + +z = Node(1, 'x') # type: AnyNode +IntNode = Node[int, S] +AnyNode = Node[S, T] +[out] + +[case testNamedTupleForwardAsUpperBoundSerialization] +from typing import NamedTuple, TypeVar, Generic +T = TypeVar('T', bound='M') +class G(Generic[T]): + x: T + +yg: G[M] +z: int = G[M]().x.x +z = G[M]().x[0] +M = NamedTuple('M', [('x', int)]) +[out] + +[case testSelfRefNTIncremental1] +from typing import Tuple, NamedTuple + +Node = NamedTuple('Node', [ # type: ignore + ('name', str), + ('children', Tuple['Node', ...]), + ]) +n: Node +[builtins fixtures/tuple.pyi] + +[case testSelfRefNTIncremental2] +from typing import Tuple, NamedTuple + +A = NamedTuple('A', [ # type: ignore + ('x', str), + ('y', Tuple['B', ...]), + ]) +class B(NamedTuple): # type: ignore + x: A + y: int + +n: A +[builtins fixtures/tuple.pyi] + +[case testSelfRefNTIncremental3] +from typing import NamedTuple, Tuple + +class B(NamedTuple): # type: ignore + x: Tuple[A, int] + y: int +A = NamedTuple('A', [ # type: ignore + ('x', str), + ('y', 'B'), + ]) +n: B +m: A +lst = [m, n] +[builtins fixtures/tuple.pyi] + +[case testSelfRefNTIncremental4] +from typing import NamedTuple + +class B(NamedTuple): # type: ignore + x: A + y: int +class A(NamedTuple): # type: ignore + x: str + y: B + +n: A +[builtins fixtures/tuple.pyi] + +[case testSelfRefNTIncremental5] +from typing import NamedTuple + +B = NamedTuple('B', [ # type: ignore + ('x', A), + ('y', int), + ]) +A = NamedTuple('A', [ # type: ignore + ('x', str), + ('y', 'B'), + ]) +n: A +def f(m: B) -> None: pass +[builtins fixtures/tuple.pyi] + [case testCrashWithPartialGlobalAndCycle] import bar diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 60eef4eb4555..8279e1aeafd4 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -1446,10 +1446,10 @@ def f(x: Union[Type[int], Type[str], Type[List]]) -> None: reveal_type(x()) # E: Revealed type is 'Union[builtins.int, builtins.str]' x()[1] # E: Value of type "Union[int, str]" is not indexable else: - reveal_type(x) # E: Revealed type is 'Type[builtins.list]' + reveal_type(x) # E: Revealed type is 'Type[builtins.list[Any]]' reveal_type(x()) # E: Revealed type is 'builtins.list[]' x()[1] - reveal_type(x) # E: Revealed type is 'Union[Type[builtins.int], Type[builtins.str], Type[builtins.list]]' + reveal_type(x) # E: Revealed type is 'Union[Type[builtins.int], Type[builtins.str], Type[builtins.list[Any]]]' reveal_type(x()) # E: Revealed type is 'Union[builtins.int, builtins.str, builtins.list[]]' if issubclass(x, (str, (list,))): reveal_type(x) # E: Revealed type is 'Union[Type[builtins.str], Type[builtins.list[Any]]]' @@ -1468,10 +1468,10 @@ def f(x: Type[Union[int, str, List]]) -> None: reveal_type(x()) # E: Revealed type is 'Union[builtins.int, builtins.str]' x()[1] # E: Value of type "Union[int, str]" is not indexable else: - reveal_type(x) # E: Revealed type is 'Type[builtins.list]' + reveal_type(x) # E: Revealed type is 'Type[builtins.list[Any]]' reveal_type(x()) # E: Revealed type is 'builtins.list[]' x()[1] - reveal_type(x) # E: Revealed type is 'Union[Type[builtins.int], Type[builtins.str], Type[builtins.list]]' + reveal_type(x) # E: Revealed type is 'Union[Type[builtins.int], Type[builtins.str], Type[builtins.list[Any]]]' reveal_type(x()) # E: Revealed type is 'Union[builtins.int, builtins.str, builtins.list[]]' if issubclass(x, (str, (list,))): reveal_type(x) # E: Revealed type is 'Union[Type[builtins.str], Type[builtins.list[Any]]]' @@ -1485,17 +1485,17 @@ def f(x: Type[Union[int, str, List]]) -> None: from typing import Union, List, Tuple, Dict, Type def f(x: Type[Union[int, str, List]]) -> None: - reveal_type(x) # E: Revealed type is 'Union[Type[builtins.int], Type[builtins.str], Type[builtins.list]]' + reveal_type(x) # E: Revealed type is 'Union[Type[builtins.int], Type[builtins.str], Type[builtins.list[Any]]]' reveal_type(x()) # E: Revealed type is 'Union[builtins.int, builtins.str, builtins.list[]]' if issubclass(x, (str, (int,))): reveal_type(x) # E: Revealed type is 'Union[Type[builtins.int], Type[builtins.str]]' reveal_type(x()) # E: Revealed type is 'Union[builtins.int, builtins.str]' x()[1] # E: Value of type "Union[int, str]" is not indexable else: - reveal_type(x) # E: Revealed type is 'Type[builtins.list]' + reveal_type(x) # E: Revealed type is 'Type[builtins.list[Any]]' reveal_type(x()) # E: Revealed type is 'builtins.list[]' x()[1] - reveal_type(x) # E: Revealed type is 'Union[Type[builtins.int], Type[builtins.str], Type[builtins.list]]' + reveal_type(x) # E: Revealed type is 'Union[Type[builtins.int], Type[builtins.str], Type[builtins.list[Any]]]' reveal_type(x()) # E: Revealed type is 'Union[builtins.int, builtins.str, builtins.list[]]' if issubclass(x, (str, (list,))): reveal_type(x) # E: Revealed type is 'Union[Type[builtins.str], Type[builtins.list[Any]]]' diff --git a/test-data/unit/check-namedtuple.test b/test-data/unit/check-namedtuple.test index f68279eef913..9726dad27b20 100644 --- a/test-data/unit/check-namedtuple.test +++ b/test-data/unit/check-namedtuple.test @@ -415,7 +415,7 @@ b = B._make(['']) # type: B [case testNamedTupleIncompatibleRedefinition] from typing import NamedTuple class Crash(NamedTuple): - count: int # E: Incompatible types in assignment (expression has type "int", base class "tuple" defined the type as "Callable[[Tuple[Any, ...], Any], int]") + count: int # E: Incompatible types in assignment (expression has type "int", base class "tuple" defined the type as "Callable[[Tuple[int, ...], Any], int]") [builtins fixtures/tuple.pyi] [case testNamedTupleInClassNamespace] @@ -434,6 +434,20 @@ def f() -> None: A = NamedTuple('A', [('x', int)]) A # E: Name 'A' is not defined +[case testNamedTupleForwardAsUpperBound] +from typing import NamedTuple, TypeVar, Generic +T = TypeVar('T', bound='M') +class G(Generic[T]): + x: T + +yb: G[int] # E: Type argument "builtins.int" of "G" must be a subtype of "Tuple[builtins.int, fallback=__main__.M]" +yg: G[M] +reveal_type(G[M]().x.x) # E: Revealed type is 'builtins.int' +reveal_type(G[M]().x[0]) # E: Revealed type is 'builtins.int' + +M = NamedTuple('M', [('x', int)]) +[out] + [case testNamedTupleWithImportCycle] import a [file a.py] @@ -447,14 +461,173 @@ class X(N): pass import a def f(x: a.X) -> None: - # The type of x is broken (https://github.com/python/mypy/issues/3016) but we need to - # do something reasonable here to avoid a regression. reveal_type(x) x = a.X(1) reveal_type(x) [out] -tmp/b.py:6: error: Revealed type is 'a.X' -tmp/b.py:8: error: Revealed type is 'Tuple[Any, fallback=a.X]' +tmp/b.py:4: error: Revealed type is 'Tuple[Any, fallback=a.X]' +tmp/b.py:6: error: Revealed type is 'Tuple[Any, fallback=a.X]' + +[case testNamedTupleWithImportCycle2] +import a +[file a.py] +from collections import namedtuple +from b import f + +N = namedtuple('N', 'a') +[file b.py] +import a + +def f(x: a.N) -> None: + reveal_type(x) + x = a.N(1) + reveal_type(x) +[out] +tmp/b.py:4: error: Revealed type is 'Tuple[Any, fallback=a.N]' +tmp/b.py:6: error: Revealed type is 'Tuple[Any, fallback=a.N]' + +[case testSimpleSelfReferrentialNamedTuple] +from typing import NamedTuple +class MyNamedTuple(NamedTuple): + parent: 'MyNamedTuple' + +def bar(nt: MyNamedTuple) -> MyNamedTuple: + return nt + +x: MyNamedTuple +reveal_type(x.parent) +[out] +main:2: error: Recursive types not fully supported yet, nested types replaced with "Any" +main:9: error: Revealed type is 'Tuple[Any, fallback=__main__.MyNamedTuple]' + +-- Some crazy selef-referential named tuples and types dicts +-- to be sure that everything works + +[case testCrossFileNamedTupleForwardRefs] +import a +[file a.py] +import b +from typing import Any, NamedTuple + +class A: + def a(self, b: 'b.B') -> str: + return 'a' +ATuple = NamedTuple('ATuple', [('a', Any)]) + +[file b.py] +import a + +class B: + def b(self, a: 'a.A') -> str: + return 'b' + def aWithTuple(self, atuple: 'a.ATuple') -> str: + return 'a' +[out] + +[case testSelfRefNT1] +from typing import Tuple, NamedTuple + +Node = NamedTuple('Node', [ # E: Recursive types not fully supported yet, nested types replaced with "Any" + ('name', str), + ('children', Tuple['Node', ...]), + ]) +n: Node +reveal_type(n) # E: Revealed type is 'Tuple[builtins.str, builtins.tuple[Tuple[builtins.str, builtins.tuple[Any], fallback=__main__.Node]], fallback=__main__.Node]' +[builtins fixtures/tuple.pyi] + + +[case testSelfRefNT2] +from typing import Tuple, NamedTuple + +A = NamedTuple('A', [ # E + ('x', str), + ('y', Tuple['B', ...]), + ]) +class B(NamedTuple): # E + x: A + y: int + +n: A +reveal_type(n) # E: Revealed type is 'Tuple[builtins.str, builtins.tuple[Tuple[Tuple[builtins.str, builtins.tuple[Any], fallback=__main__.A], builtins.int, fallback=__main__.B]], fallback=__main__.A]' +[builtins fixtures/tuple.pyi] +[out] +main:3: error: Recursive types not fully supported yet, nested types replaced with "Any" +main:7: error: Recursive types not fully supported yet, nested types replaced with "Any" + +[case testSelfRefNT3] +from typing import NamedTuple, Tuple + +class B(NamedTuple): # E + x: Tuple[A, int] + y: int + +A = NamedTuple('A', [ # E: Recursive types not fully supported yet, nested types replaced with "Any" + ('x', str), + ('y', 'B'), + ]) +n: B +m: A +reveal_type(n.x) # E: Revealed type is 'Tuple[Tuple[builtins.str, Tuple[Tuple[Any, builtins.int], builtins.int, fallback=__main__.B], fallback=__main__.A], builtins.int]' +reveal_type(m[0]) # E: Revealed type is 'builtins.str' +lst = [m, n] +reveal_type(lst[0]) # E: Revealed type is 'Tuple[builtins.object, builtins.object]' +[builtins fixtures/tuple.pyi] +[out] +main:3: error: Recursive types not fully supported yet, nested types replaced with "Any" + +[case testSelfRefNT4] +from typing import NamedTuple + +class B(NamedTuple): # E + x: A + y: int + +class A(NamedTuple): # E + x: str + y: B + +n: A +reveal_type(n.y[0]) # E: Revealed type is 'Tuple[builtins.str, Tuple[Any, builtins.int, fallback=__main__.B], fallback=__main__.A]' +[builtins fixtures/tuple.pyi] +[out] +main:3: error: Recursive types not fully supported yet, nested types replaced with "Any" +main:7: error: Recursive types not fully supported yet, nested types replaced with "Any" + +[case testSelfRefNT5] +from typing import NamedTuple + +B = NamedTuple('B', [ # E: Recursive types not fully supported yet, nested types replaced with "Any" + ('x', A), + ('y', int), + ]) +A = NamedTuple('A', [ # E: Recursive types not fully supported yet, nested types replaced with "Any" + ('x', str), + ('y', 'B'), + ]) +n: A +def f(m: B) -> None: pass +reveal_type(n) # E: Revealed type is 'Tuple[builtins.str, Tuple[Tuple[builtins.str, Tuple[Any, builtins.int, fallback=__main__.B], fallback=__main__.A], builtins.int, fallback=__main__.B], fallback=__main__.A]' +reveal_type(f) # E: Revealed type is 'def (m: Tuple[Tuple[builtins.str, Tuple[Any, builtins.int, fallback=__main__.B], fallback=__main__.A], builtins.int, fallback=__main__.B])' +[builtins fixtures/tuple.pyi] + +[case testRecursiveNamedTupleInBases] +from typing import List, NamedTuple, Union + +Exp = Union['A', 'B'] # E: Recursive types not fully supported yet, nested types replaced with "Any" +class A(NamedTuple('A', [('attr', List[Exp])])): pass +class B(NamedTuple('B', [('val', object)])): pass + +def my_eval(exp: Exp) -> int: + reveal_type(exp) # E: Revealed type is 'Union[Tuple[builtins.list[Any], fallback=__main__.A], Tuple[builtins.object, fallback=__main__.B]]' + if isinstance(exp, A): + my_eval(exp[0][0]) + return my_eval(exp.attr[0]) + if isinstance(exp, B): + return exp.val # E: Incompatible return value type (got "object", expected "int") + +my_eval(A([B(1), B(2)])) # OK +[builtins fixtures/isinstancelist.pyi] +[out] [case testForwardReferenceInNamedTuple] from typing import NamedTuple diff --git a/test-data/unit/check-newtype.test b/test-data/unit/check-newtype.test index c9e01edf3ef4..badd9488adbd 100644 --- a/test-data/unit/check-newtype.test +++ b/test-data/unit/check-newtype.test @@ -276,17 +276,17 @@ f = NewType('f', tp=int) # E: NewType(...) expects exactly two positional argum [case testNewTypeWithAnyFails] from typing import NewType, Any -A = NewType('A', Any) # E: Argument 2 to NewType(...) must be subclassable (got Any) +A = NewType('A', Any) # E: Argument 2 to NewType(...) must be subclassable (got "Any") [out] [case testNewTypeWithUnionsFails] from typing import NewType, Union -Foo = NewType('Foo', Union[int, float]) # E: Argument 2 to NewType(...) must be subclassable (got Union[builtins.int, builtins.float]) +Foo = NewType('Foo', Union[int, float]) # E: Argument 2 to NewType(...) must be subclassable (got "Union[int, float]") [out] [case testNewTypeWithTypeTypeFails] from typing import NewType, Type -Foo = NewType('Foo', Type[int]) # E: Argument 2 to NewType(...) must be subclassable (got Type[builtins.int]) +Foo = NewType('Foo', Type[int]) # E: Argument 2 to NewType(...) must be subclassable (got "Type[int]") a = Foo(type(3)) [builtins fixtures/args.pyi] [out] diff --git a/test-data/unit/check-statements.test b/test-data/unit/check-statements.test index 5d45c15d128d..cd38fc02f3fb 100644 --- a/test-data/unit/check-statements.test +++ b/test-data/unit/check-statements.test @@ -1565,3 +1565,54 @@ reveal_type(d['weight0']) # E: Revealed type is 'builtins.float*' [builtins fixtures/floatdict.pyi] +[case testForwardRefsInForStatementImplicit] +from typing import List, NamedTuple +lst: List[N] + +for i in lst: + reveal_type(i.x) # E: Revealed type is 'builtins.int' + a: str = i[0] # E: Incompatible types in assignment (expression has type "int", variable has type "str") + +N = NamedTuple('N', [('x', int)]) +[builtins fixtures/list.pyi] +[out] + +[case testForwardRefsInForStatement] +from typing import List, NamedTuple +lst: List[M] + +for i in lst: # type: N + reveal_type(i.x) # E: Revealed type is 'builtins.int' + a: str = i[0] # E: Incompatible types in assignment (expression has type "int", variable has type "str") + +N = NamedTuple('N', [('x', int)]) +class M(N): pass +[builtins fixtures/list.pyi] +[out] + +[case testForwardRefsInWithStatementImplicit] +from typing import ContextManager, Any +from mypy_extensions import TypedDict +cm: ContextManager[N] + +with cm as g: + a: int = g['x'] + +N = TypedDict('N', {'x': int}) +[builtins fixtures/dict.pyi] +[typing fixtures/typing-full.pyi] +[out] + +[case testForwardRefsInWithStatement] +from typing import ContextManager, Any +from mypy_extensions import TypedDict +cm: ContextManager[Any] + +with cm as g: # type: N + a: str = g['x'] # E: Incompatible types in assignment (expression has type "int", variable has type "str") + +N = TypedDict('N', {'x': int}) +[builtins fixtures/dict.pyi] +[typing fixtures/typing-full.pyi] +[out] + diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index 14483d9ceea1..387eabca4764 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -704,18 +704,17 @@ from typing import Tuple class A(tuple): pass [out] -[case testTupleBaseClass2-skip] +[case testTupleBaseClass2] import m [file m.pyi] -# This doesn't work correctly -- no errors are reported (#867) from typing import Tuple a = None # type: A class A(Tuple[int, str]): pass x, y = a -x() # Expected: "int" not callable -y() # Expected: "str" not callable +x() # E: "int" not callable +y() # E: "str" not callable +[builtins fixtures/tuple.pyi] [out] -(should fail) [case testGenericClassWithTupleBaseClass] from typing import TypeVar, Generic, Tuple diff --git a/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test index 2179eecd9c21..400326660b30 100644 --- a/test-data/unit/check-type-aliases.test +++ b/test-data/unit/check-type-aliases.test @@ -113,6 +113,110 @@ f = None # type: EmptyTupleCallable reveal_type(f) # E: Revealed type is 'def (Tuple[])' [builtins fixtures/list.pyi] +[case testForwardTypeAlias] +def f(p: 'Alias') -> None: + pass + +reveal_type(f) # E: Revealed type is 'def (p: builtins.int)' +Alias = int +[out] + +[case testForwardTypeAliasGeneric] +from typing import TypeVar, Tuple +def f(p: 'Alias[str]') -> None: + pass + +reveal_type(f) # E: Revealed type is 'def (p: Tuple[builtins.int, builtins.str])' +T = TypeVar('T') +Alias = Tuple[int, T] +[out] + +[case testRecursiveAliasesErrors1] +from typing import Type, Callable, Union + +A = Union[A, int] +B = Callable[[B], int] +C = Type[C] +[out] +main:3: error: Recursive types not fully supported yet, nested types replaced with "Any" +main:4: error: Recursive types not fully supported yet, nested types replaced with "Any" +main:5: error: Recursive types not fully supported yet, nested types replaced with "Any" + +[case testRecursiveAliasesErrors2] +from typing import Type, Callable, Union + +A = Union[B, int] +B = Callable[[C], int] +C = Type[A] +[out] +main:3: error: Recursive types not fully supported yet, nested types replaced with "Any" +main:4: error: Recursive types not fully supported yet, nested types replaced with "Any" +main:5: error: Recursive types not fully supported yet, nested types replaced with "Any" + +[case testDoubleForwardAlias] +from typing import List +x: A +A = List[B] +B = List[int] +reveal_type(x) # E: Revealed type is 'builtins.list[builtins.list[builtins.int]]' +[builtins fixtures/list.pyi] +[out] + +[case testDoubleForwardAliasWithNamedTuple] +from typing import List, NamedTuple +x: A +A = List[B] +class B(NamedTuple): + x: str +reveal_type(x[0].x) # E: Revealed type is 'builtins.str' +[builtins fixtures/list.pyi] +[out] + +[case testJSONAliasApproximation] +from typing import List, Union, Dict +x: JSON +JSON = Union[int, str, List[JSON], Dict[str, JSON]] # type: ignore +reveal_type(x) # E: Revealed type is 'Union[builtins.int, builtins.str, builtins.list[Any], builtins.dict[builtins.str, Any]]' +if isinstance(x, list): + reveal_type(x) # E: Revealed type is 'builtins.list[Any]' +[builtins fixtures/isinstancelist.pyi] +[out] + +[case testProhibitedForwardRefToTypeVar] +from typing import TypeVar, List + +a: List[T] + +T = TypeVar('T') +[builtins fixtures/list.pyi] +[out] +main:3: error: Invalid type "__main__.T" +main:3: note: Forward references to type variables are prohibited + +[case testUnsupportedForwardRef] +from typing import List, TypeVar + +T = TypeVar('T') + +def f(x: T) -> None: + y: A[T] # E: Unsupported forward reference to "A" + +A = List[T] +[builtins fixtures/list.pyi] +[out] + +[case testUnsupportedForwardRef2] +from typing import List, TypeVar + +def f() -> None: + X = List[int] + x: A[X] # E: Unsupported forward reference to "A" + +T = TypeVar('T') +A = List[T] +[builtins fixtures/list.pyi] +[out] + [case testNoneAlias] from typing import Union void = type(None) diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 33b27a1ad23e..10823ce96883 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -1304,10 +1304,58 @@ m2: Mapping[str, C] = x # E: Incompatible types in assignment (expression has ty [case testForwardReferenceToTypedDictInTypedDict] from typing import Mapping from mypy_extensions import TypedDict -# Forward references don't quite work yet -X = TypedDict('X', {'a': 'A'}) # E: Invalid type "__main__.A" +X = TypedDict('X', {'a': 'A'}) A = TypedDict('A', {'b': int}) x: X reveal_type(x) # E: Revealed type is 'TypedDict('__main__.X', {'a': TypedDict('__main__.A', {'b': builtins.int})})' reveal_type(x['a']['b']) # E: Revealed type is 'builtins.int' [builtins fixtures/dict.pyi] + +[case testSelfRecursiveTypedDictInheriting] +from mypy_extensions import TypedDict + +class MovieBase(TypedDict): + name: str + year: int + +class Movie(MovieBase): # type: ignore # warning about recursive not fully supported + director: 'Movie' + +m: Movie +reveal_type(m['director']['name']) # E: Revealed type is 'builtins.str' +[builtins fixtures/dict.pyi] +[out] + +[case testTypedDictForwardAsUpperBound] +from typing import TypeVar, Generic +from mypy_extensions import TypedDict +T = TypeVar('T', bound='M') +class G(Generic[T]): + x: T + +yb: G[int] # E: Type argument "builtins.int" of "G" must be a subtype of "TypedDict({'x': builtins.int}, fallback=typing.Mapping[builtins.str, builtins.object])" +yg: G[M] +z: int = G[M]().x['x'] + +class M(TypedDict): + x: int +[builtins fixtures/dict.pyi] +[out] + +[case testTypedDictWithImportCycleForward] +import a +[file a.py] +from mypy_extensions import TypedDict +from b import f + +N = TypedDict('N', {'a': str}) +[file b.py] +import a + +def f(x: a.N) -> None: + reveal_type(x) + reveal_type(x['a']) +[builtins fixtures/dict.pyi] +[out] +tmp/b.py:4: error: Revealed type is 'TypedDict('a.N', {'a': builtins.str})' +tmp/b.py:5: error: Revealed type is 'builtins.str' diff --git a/test-data/unit/cmdline.test b/test-data/unit/cmdline.test index ade7f27a0338..1bd5b9c5ad59 100644 --- a/test-data/unit/cmdline.test +++ b/test-data/unit/cmdline.test @@ -806,7 +806,7 @@ Baz = NewType('Baz', Any) # this error does not come from `--disallow-any=expli Bar = NewType('Bar', List[Any]) [out] -m.py:3: error: Argument 2 to NewType(...) must be subclassable (got Any) +m.py:3: error: Argument 2 to NewType(...) must be subclassable (got "Any") m.py:4: error: Explicit "Any" is not allowed [case testDisallowAnyExplicitTypedDictSimple] diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index 4ea16032e0d7..80bbaeeb0c94 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -1380,6 +1380,14 @@ _testTypedDictMappingMethods.py:9: error: Revealed type is 'typing.AbstractSet[b _testTypedDictMappingMethods.py:10: error: Revealed type is 'typing.AbstractSet[Tuple[builtins.str*, builtins.int*]]' _testTypedDictMappingMethods.py:11: error: Revealed type is 'typing.ValuesView[builtins.int*]' +[case testCrashOnComplexCheckWithNamedTupleNext] +from typing import NamedTuple + +MyNamedTuple = NamedTuple('MyNamedTuple', [('parent', 'MyNamedTuple')]) # type: ignore +def foo(mymap) -> MyNamedTuple: + return next((mymap[key] for key in mymap), None) +[out] + [case testCanConvertTypedDictToAnySuperclassOfMapping] from mypy_extensions import TypedDict from typing import Sized, Iterable, Container diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test index 0dba8f2836bf..ccd13f1b3028 100644 --- a/test-data/unit/semanal-errors.test +++ b/test-data/unit/semanal-errors.test @@ -1401,3 +1401,20 @@ class A: ... # E: Name 'A' already defined on line 2 [builtins fixtures/list.pyi] [out] + +[case testNoInvalidTypeInDynamicFunctions] +from typing import Dict, TypeVar +T = TypeVar('T') + +def f(): # Note no annotation + x: Dict[str, T] = {} + y: T + z: x + def nested(): pass + t: nested + +def g() -> None: + x: Dict[str, T] = {} # E: Invalid type "__main__.T" + +[builtins fixtures/dict.pyi] +[out]