Skip to content

Make FakeInfo a more general-purpose TypeInfo placeholder #5469

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions mypy/fastparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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'
Expand Down
6 changes: 4 additions & 2 deletions mypy/fastparse2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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'
Expand Down
2 changes: 1 addition & 1 deletion mypy/indirection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
40 changes: 30 additions & 10 deletions mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 [])
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}


Expand Down
2 changes: 1 addition & 1 deletion mypy/semanal_pass3.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion mypy/server/astmerge.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down
2 changes: 1 addition & 1 deletion mypy/server/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
9 changes: 7 additions & 2 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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:
Expand Down
6 changes: 2 additions & 4 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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('<NOT READY>', mypy.nodes.Block([])),
'<NOT READY>')
NOT_READY = mypy.nodes.FakeInfo('De-serialization failure: TypeInfo not fixed')


class Instance(Type):
Expand All @@ -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
Expand Down