diff --git a/mypy/checker.py b/mypy/checker.py index 9b0a728552e6..a177d00e8202 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -435,7 +435,7 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None: assert isinstance(inner_type, CallableType) impl_type = inner_type - is_descriptor_get = defn.info is not None and defn.name() == "__get__" + is_descriptor_get = defn.info and defn.name() == "__get__" for i, item in enumerate(defn.items): # TODO overloads involving decorators assert isinstance(item, Decorator) @@ -979,7 +979,7 @@ def check_reverse_op_method(self, defn: FuncItem, # just decides whether it's worth calling # check_overlapping_op_methods(). - assert defn.info is not None + assert defn.info # First check for a valid signature method_type = CallableType([AnyType(TypeOfAny.special_form), @@ -2765,7 +2765,7 @@ def check_for_untyped_decorator(self, self.msg.typed_function_untyped_decorator(func.name(), dec_expr) def check_incompatible_property_override(self, e: Decorator) -> None: - if not e.var.is_settable_property and e.func.info is not None: + if not e.var.is_settable_property and e.func.info: name = e.func.name() for base in e.func.info.mro[1:]: base_attr = base.names.get(name) @@ -3322,7 +3322,7 @@ def enter_partial_types(self, *, is_function: bool = False, var.type = AnyType(TypeOfAny.from_error) def is_defined_in_base_class(self, var: Var) -> bool: - if var.info is not None: + if var.info: for base in var.info.mro[1:]: if base.get(var.name()) is not None: return True diff --git a/mypy/fastparse.py b/mypy/fastparse.py index e7d4f5253fcb..4fe4d65f781c 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -23,10 +23,11 @@ AwaitExpr, TempNode, Expression, Statement, ARG_POS, ARG_OPT, ARG_STAR, ARG_NAMED, ARG_NAMED_OPT, ARG_STAR2, check_arg_names, + FakeInfo, ) from mypy.types import ( Type, CallableType, AnyType, UnboundType, TupleType, TypeList, EllipsisType, CallableArgument, - TypeOfAny + TypeOfAny, Instance, ) from mypy import defaults from mypy import messages @@ -59,7 +60,8 @@ # There is no way to create reasonable fallbacks at this stage, # they must be patched later. -_dummy_fallback = None # type: Any +MISSING_FALLBACK = FakeInfo("fallback can't be filled out until semanal") +_dummy_fallback = Instance(MISSING_FALLBACK, [], -1) TYPE_COMMENT_SYNTAX_ERROR = 'syntax error in type comment' TYPE_COMMENT_AST_ERROR = 'invalid type comment or annotation' diff --git a/mypy/fastparse2.py b/mypy/fastparse2.py index 5d17fe9479ff..d4a86aa59150 100644 --- a/mypy/fastparse2.py +++ b/mypy/fastparse2.py @@ -34,9 +34,10 @@ SetComprehension, ComplexExpr, EllipsisExpr, YieldExpr, Argument, Expression, Statement, BackquoteExpr, PrintStmt, ExecStmt, ARG_POS, ARG_OPT, ARG_STAR, ARG_NAMED, ARG_STAR2, OverloadPart, check_arg_names, + FakeInfo, ) from mypy.types import ( - Type, CallableType, AnyType, UnboundType, EllipsisType, TypeOfAny + Type, CallableType, AnyType, UnboundType, EllipsisType, TypeOfAny, Instance, ) from mypy import messages from mypy.errors import Errors @@ -70,7 +71,8 @@ # There is no way to create reasonable fallbacks at this stage, # they must be patched later. -_dummy_fallback = None # type: Any +MISSING_FALLBACK = FakeInfo("fallback can't be filled out until semanal") +_dummy_fallback = Instance(MISSING_FALLBACK, [], -1) TYPE_COMMENT_SYNTAX_ERROR = 'syntax error in type comment' TYPE_COMMENT_AST_ERROR = 'invalid type comment' diff --git a/mypy/indirection.py b/mypy/indirection.py index d9c171fc4531..6453a25bb449 100644 --- a/mypy/indirection.py +++ b/mypy/indirection.py @@ -64,7 +64,7 @@ def visit_type_var(self, t: types.TypeVarType) -> Set[str]: def visit_instance(self, t: types.Instance) -> Set[str]: out = self._visit(*t.args) - if t.type is not None: + if t.type: # Uses of a class depend on everything in the MRO, # as changes to classes in the MRO can add types to methods, # change property types, change the MRO itself, etc. diff --git a/mypy/nodes.py b/mypy/nodes.py index 9f8d10666251..8a81d01f2cb1 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -398,7 +398,7 @@ def __init__(self) -> None: self.unanalyzed_type = None # type: Optional[mypy.types.Type] # If method, reference to TypeInfo # TODO: Type should be Optional[TypeInfo] - self.info = cast(TypeInfo, None) + self.info = FUNC_NO_INFO self.is_property = False self.is_class = False self.is_static = False @@ -727,7 +727,7 @@ def __init__(self, name: str, type: 'Optional[mypy.types.Type]' = None) -> None: # TODO: Should be Optional[str] self._fullname = cast(str, None) # Name with module prefix # TODO: Should be Optional[TypeInfo] - self.info = cast(TypeInfo, None) # Defining class (for member variables) + self.info = VAR_NO_INFO self.type = type # type: Optional[mypy.types.Type] # Declared or inferred type, or None # Is this the first argument to an ordinary method (usually "self")? self.is_self = False @@ -807,6 +807,7 @@ def __init__(self, self.type_vars = type_vars or [] self.base_type_exprs = base_type_exprs or [] self.removed_base_type_exprs = [] + self.info = CLASSDEF_NO_INFO self.metaclass = metaclass self.decorators = [] self.keywords = OrderedDict(keywords or []) @@ -1414,7 +1415,7 @@ class IndexExpr(Expression): base = None # type: Expression index = None # type: Expression # Inferred __getitem__ method type - method_type = None # type: mypy.types.Type + method_type = None # type: Optional[mypy.types.Type] # If not None, this is actually semantically a type application # Class[type, ...] or a type alias initializer. analyzed = None # type: Union[TypeApplication, TypeAliasExpr, None] @@ -2156,7 +2157,7 @@ class is generic then it will be a type constructor of higher kind. # 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 + replaced = None # type: Optional[TypeInfo] # This is a dictionary that will be serialized and un-serialized as is. # It is useful for plugins to add their data to save in the cache. @@ -2416,11 +2417,27 @@ class FakeInfo(TypeInfo): # pass cleanly. # 2. If NOT_READY value is accidentally used somewhere, it will be obvious where the value # is from, whereas a 'None' value could come from anywhere. - def __init__(self, *args: Any, **kwargs: Any) -> None: - pass + # + # Additionally, this serves as a more general-purpose placeholder + # for missing TypeInfos in a number of places where the excuses + # for not being Optional are a little weaker. + # + # It defines a __bool__ method so that it can be conveniently + # tested against in the same way that it would be if things were + # properly optional. + def __init__(self, msg: str) -> None: + self.msg = msg + + def __bool__(self) -> bool: + return False def __getattribute__(self, attr: str) -> None: - raise AssertionError('De-serialization failure: TypeInfo not fixed') + raise AssertionError(object.__getattribute__(self, 'msg')) + + +VAR_NO_INFO = FakeInfo('Var is lacking info') # type: TypeInfo +CLASSDEF_NO_INFO = FakeInfo('ClassDef is lacking info') # type: TypeInfo +FUNC_NO_INFO = FakeInfo('FuncBase for non-methods lack info') # type: TypeInfo class TypeAlias(SymbolNode): @@ -2660,8 +2677,10 @@ def fullname(self) -> Optional[str]: @property def type(self) -> 'Optional[mypy.types.Type]': node = self.node - if ((isinstance(node, Var) or isinstance(node, FuncBase)) - and node.type is not None): + if (isinstance(node, Var) and node.type is not None): + return node.type + # mypy thinks this branch is unreachable but it is wrong (#3603) + elif (isinstance(node, FuncBase) and node.type is not None): return node.type elif isinstance(node, Decorator): return node.var.type @@ -2809,7 +2828,8 @@ def get_member_expr_fullname(expr: MemberExpr) -> Optional[str]: deserialize_map = { key: obj.deserialize # type: ignore for key, obj in globals().items() - if isinstance(obj, type) and issubclass(obj, SymbolNode) and obj is not SymbolNode + if type(obj) is not FakeInfo + and isinstance(obj, type) and issubclass(obj, SymbolNode) and obj is not SymbolNode } diff --git a/mypy/semanal_pass3.py b/mypy/semanal_pass3.py index 5aaeb16ac2d8..cb7c02a9c671 100644 --- a/mypy/semanal_pass3.py +++ b/mypy/semanal_pass3.py @@ -251,7 +251,7 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: if (isinstance(s.lvalues[0], NameExpr) and s.lvalues[0].kind == MDEF and isinstance(s.lvalues[0].node, Var)): var = s.lvalues[0].node - if var.info is not None and var.is_inferred and not var.is_classvar: + if var.info and var.is_inferred and not var.is_classvar: for base in var.info.mro[1:]: tnode = base.names.get(var.name()) if (tnode is not None and isinstance(tnode.node, Var) diff --git a/mypy/server/astmerge.py b/mypy/server/astmerge.py index 5265f26194b1..df9dfca26139 100644 --- a/mypy/server/astmerge.py +++ b/mypy/server/astmerge.py @@ -300,7 +300,8 @@ def process_type_info(self, info: Optional[TypeInfo]) -> None: self.fixup_type(info.tuple_type) self.fixup_type(info.typeddict_type) info.defn.info = self.fixup(info) - info.replaced = self.fixup(info.replaced) + if info.replaced: + info.replaced = self.fixup(info.replaced) replace_nodes_in_symbol_table(info.names, self.replacements) for i, item in enumerate(info.mro): info.mro[i] = self.fixup(info.mro[i]) diff --git a/mypy/server/update.py b/mypy/server/update.py index 73939828d4bc..b9f930fd3fab 100644 --- a/mypy/server/update.py +++ b/mypy/server/update.py @@ -1070,7 +1070,7 @@ def target_from_node(module: str, return None return module elif isinstance(node, (OverloadedFuncDef, FuncDef)): - if node.info is not None: + if node.info: return '%s.%s' % (node.info.fullname(), node.name()) else: return '%s.%s' % (module, node.name()) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 934e9ccd311e..6131058070f2 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -413,7 +413,10 @@ def visit_callable_type(self, t: CallableType, nested: bool = True) -> Type: variables = self.bind_function_type_variables(t, t) ret = t.copy_modified(arg_types=self.anal_array(t.arg_types, nested=nested), ret_type=self.anal_type(t.ret_type, nested=nested), - fallback=t.fallback or self.named_type('builtins.function'), + # If the fallback isn't filled in yet, + # its type will be the falsey FakeInfo + fallback=(t.fallback if t.fallback.type + else self.named_type('builtins.function')), variables=self.anal_var_defs(variables)) return ret @@ -437,7 +440,9 @@ def visit_tuple_type(self, t: TupleType) -> Type: else: return AnyType(TypeOfAny.from_error) any_type = AnyType(TypeOfAny.special_form) - fallback = t.fallback if t.fallback else self.named_type('builtins.tuple', [any_type]) + # If the fallback isn't filled in yet, its type will be the falsey FakeInfo + fallback = (t.fallback if t.fallback.type + else self.named_type('builtins.tuple', [any_type])) return TupleType(self.anal_array(t.items), fallback, t.line) def visit_typeddict_type(self, t: TypedDictType) -> Type: diff --git a/mypy/types.py b/mypy/types.py index 54d7d9a9e40b..f8ba15600064 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -490,9 +490,7 @@ def deserialize(cls, data: JsonDict) -> 'DeletedType': # Fake TypeInfo to be used as a placeholder during Instance de-serialization. -NOT_READY = mypy.nodes.FakeInfo(mypy.nodes.SymbolTable(), - mypy.nodes.ClassDef('', mypy.nodes.Block([])), - '') +NOT_READY = mypy.nodes.FakeInfo('De-serialization failure: TypeInfo not fixed') class Instance(Type): @@ -506,7 +504,7 @@ class Instance(Type): def __init__(self, typ: mypy.nodes.TypeInfo, args: List[Type], line: int = -1, column: int = -1, erased: bool = False) -> None: super().__init__(line, column) - assert typ is NOT_READY or typ.fullname() not in ["builtins.Any", "typing.Any"] + assert not typ or typ.fullname() not in ["builtins.Any", "typing.Any"] self.type = typ self.args = args self.erased = erased # True if result of type variable substitution