Skip to content

Commit e43eb89

Browse files
authored
Make FakeInfo a more general-purpose TypeInfo placeholder (#5469)
There are a lot of places in mypy currently that shove None into fields expecting TypeInfo. Since mypyc does not appreciate this kind of behavior, use FakeInfo instances instead of None. FakeInfo is generalized to allow different assertion messages and to override `__bool__` so that it can be conveniently checked against.
1 parent 5c0394e commit e43eb89

10 files changed

+56
-28
lines changed

mypy/checker.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None:
435435
assert isinstance(inner_type, CallableType)
436436
impl_type = inner_type
437437

438-
is_descriptor_get = defn.info is not None and defn.name() == "__get__"
438+
is_descriptor_get = defn.info and defn.name() == "__get__"
439439
for i, item in enumerate(defn.items):
440440
# TODO overloads involving decorators
441441
assert isinstance(item, Decorator)
@@ -979,7 +979,7 @@ def check_reverse_op_method(self, defn: FuncItem,
979979
# just decides whether it's worth calling
980980
# check_overlapping_op_methods().
981981

982-
assert defn.info is not None
982+
assert defn.info
983983

984984
# First check for a valid signature
985985
method_type = CallableType([AnyType(TypeOfAny.special_form),
@@ -2765,7 +2765,7 @@ def check_for_untyped_decorator(self,
27652765
self.msg.typed_function_untyped_decorator(func.name(), dec_expr)
27662766

27672767
def check_incompatible_property_override(self, e: Decorator) -> None:
2768-
if not e.var.is_settable_property and e.func.info is not None:
2768+
if not e.var.is_settable_property and e.func.info:
27692769
name = e.func.name()
27702770
for base in e.func.info.mro[1:]:
27712771
base_attr = base.names.get(name)
@@ -3322,7 +3322,7 @@ def enter_partial_types(self, *, is_function: bool = False,
33223322
var.type = AnyType(TypeOfAny.from_error)
33233323

33243324
def is_defined_in_base_class(self, var: Var) -> bool:
3325-
if var.info is not None:
3325+
if var.info:
33263326
for base in var.info.mro[1:]:
33273327
if base.get(var.name()) is not None:
33283328
return True

mypy/fastparse.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@
2323
AwaitExpr, TempNode, Expression, Statement,
2424
ARG_POS, ARG_OPT, ARG_STAR, ARG_NAMED, ARG_NAMED_OPT, ARG_STAR2,
2525
check_arg_names,
26+
FakeInfo,
2627
)
2728
from mypy.types import (
2829
Type, CallableType, AnyType, UnboundType, TupleType, TypeList, EllipsisType, CallableArgument,
29-
TypeOfAny
30+
TypeOfAny, Instance,
3031
)
3132
from mypy import defaults
3233
from mypy import messages
@@ -59,7 +60,8 @@
5960

6061
# There is no way to create reasonable fallbacks at this stage,
6162
# they must be patched later.
62-
_dummy_fallback = None # type: Any
63+
MISSING_FALLBACK = FakeInfo("fallback can't be filled out until semanal")
64+
_dummy_fallback = Instance(MISSING_FALLBACK, [], -1)
6365

6466
TYPE_COMMENT_SYNTAX_ERROR = 'syntax error in type comment'
6567
TYPE_COMMENT_AST_ERROR = 'invalid type comment or annotation'

mypy/fastparse2.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,10 @@
3434
SetComprehension, ComplexExpr, EllipsisExpr, YieldExpr, Argument,
3535
Expression, Statement, BackquoteExpr, PrintStmt, ExecStmt,
3636
ARG_POS, ARG_OPT, ARG_STAR, ARG_NAMED, ARG_STAR2, OverloadPart, check_arg_names,
37+
FakeInfo,
3738
)
3839
from mypy.types import (
39-
Type, CallableType, AnyType, UnboundType, EllipsisType, TypeOfAny
40+
Type, CallableType, AnyType, UnboundType, EllipsisType, TypeOfAny, Instance,
4041
)
4142
from mypy import messages
4243
from mypy.errors import Errors
@@ -70,7 +71,8 @@
7071

7172
# There is no way to create reasonable fallbacks at this stage,
7273
# they must be patched later.
73-
_dummy_fallback = None # type: Any
74+
MISSING_FALLBACK = FakeInfo("fallback can't be filled out until semanal")
75+
_dummy_fallback = Instance(MISSING_FALLBACK, [], -1)
7476

7577
TYPE_COMMENT_SYNTAX_ERROR = 'syntax error in type comment'
7678
TYPE_COMMENT_AST_ERROR = 'invalid type comment'

mypy/indirection.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def visit_type_var(self, t: types.TypeVarType) -> Set[str]:
6464

6565
def visit_instance(self, t: types.Instance) -> Set[str]:
6666
out = self._visit(*t.args)
67-
if t.type is not None:
67+
if t.type:
6868
# Uses of a class depend on everything in the MRO,
6969
# as changes to classes in the MRO can add types to methods,
7070
# change property types, change the MRO itself, etc.

mypy/nodes.py

+30-10
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ def __init__(self) -> None:
399399
self.unanalyzed_type = None # type: Optional[mypy.types.Type]
400400
# If method, reference to TypeInfo
401401
# TODO: Type should be Optional[TypeInfo]
402-
self.info = cast(TypeInfo, None)
402+
self.info = FUNC_NO_INFO
403403
self.is_property = False
404404
self.is_class = False
405405
self.is_static = False
@@ -727,7 +727,7 @@ def __init__(self, name: str, type: 'Optional[mypy.types.Type]' = None) -> None:
727727
# TODO: Should be Optional[str]
728728
self._fullname = cast(str, None) # Name with module prefix
729729
# TODO: Should be Optional[TypeInfo]
730-
self.info = cast(TypeInfo, None) # Defining class (for member variables)
730+
self.info = VAR_NO_INFO
731731
self.type = type # type: Optional[mypy.types.Type] # Declared or inferred type, or None
732732
# Is this the first argument to an ordinary method (usually "self")?
733733
self.is_self = False
@@ -807,6 +807,7 @@ def __init__(self,
807807
self.type_vars = type_vars or []
808808
self.base_type_exprs = base_type_exprs or []
809809
self.removed_base_type_exprs = []
810+
self.info = CLASSDEF_NO_INFO
810811
self.metaclass = metaclass
811812
self.decorators = []
812813
self.keywords = OrderedDict(keywords or [])
@@ -1412,7 +1413,7 @@ class IndexExpr(Expression):
14121413
base = None # type: Expression
14131414
index = None # type: Expression
14141415
# Inferred __getitem__ method type
1415-
method_type = None # type: mypy.types.Type
1416+
method_type = None # type: Optional[mypy.types.Type]
14161417
# If not None, this is actually semantically a type application
14171418
# Class[type, ...] or a type alias initializer.
14181419
analyzed = None # type: Union[TypeApplication, TypeAliasExpr, None]
@@ -2154,7 +2155,7 @@ class is generic then it will be a type constructor of higher kind.
21542155
# type (NamedTuple or TypedDict) was generated, store the corresponding
21552156
# TypeInfo here. (This attribute does not need to be serialized, it is only
21562157
# needed during the semantic passes.)
2157-
replaced = None # type: TypeInfo
2158+
replaced = None # type: Optional[TypeInfo]
21582159

21592160
# This is a dictionary that will be serialized and un-serialized as is.
21602161
# It is useful for plugins to add their data to save in the cache.
@@ -2414,11 +2415,27 @@ class FakeInfo(TypeInfo):
24142415
# pass cleanly.
24152416
# 2. If NOT_READY value is accidentally used somewhere, it will be obvious where the value
24162417
# is from, whereas a 'None' value could come from anywhere.
2417-
def __init__(self, *args: Any, **kwargs: Any) -> None:
2418-
pass
2418+
#
2419+
# Additionally, this serves as a more general-purpose placeholder
2420+
# for missing TypeInfos in a number of places where the excuses
2421+
# for not being Optional are a little weaker.
2422+
#
2423+
# It defines a __bool__ method so that it can be conveniently
2424+
# tested against in the same way that it would be if things were
2425+
# properly optional.
2426+
def __init__(self, msg: str) -> None:
2427+
self.msg = msg
2428+
2429+
def __bool__(self) -> bool:
2430+
return False
24192431

24202432
def __getattribute__(self, attr: str) -> None:
2421-
raise AssertionError('De-serialization failure: TypeInfo not fixed')
2433+
raise AssertionError(object.__getattribute__(self, 'msg'))
2434+
2435+
2436+
VAR_NO_INFO = FakeInfo('Var is lacking info') # type: TypeInfo
2437+
CLASSDEF_NO_INFO = FakeInfo('ClassDef is lacking info') # type: TypeInfo
2438+
FUNC_NO_INFO = FakeInfo('FuncBase for non-methods lack info') # type: TypeInfo
24222439

24232440

24242441
class TypeAlias(SymbolNode):
@@ -2658,8 +2675,10 @@ def fullname(self) -> Optional[str]:
26582675
@property
26592676
def type(self) -> 'Optional[mypy.types.Type]':
26602677
node = self.node
2661-
if ((isinstance(node, Var) or isinstance(node, FuncBase))
2662-
and node.type is not None):
2678+
if (isinstance(node, Var) and node.type is not None):
2679+
return node.type
2680+
# mypy thinks this branch is unreachable but it is wrong (#3603)
2681+
elif (isinstance(node, FuncBase) and node.type is not None):
26632682
return node.type
26642683
elif isinstance(node, Decorator):
26652684
return node.var.type
@@ -2764,7 +2783,8 @@ def get_member_expr_fullname(expr: MemberExpr) -> Optional[str]:
27642783
deserialize_map = {
27652784
key: obj.deserialize # type: ignore
27662785
for key, obj in globals().items()
2767-
if isinstance(obj, type) and issubclass(obj, SymbolNode) and obj is not SymbolNode
2786+
if type(obj) is not FakeInfo
2787+
and isinstance(obj, type) and issubclass(obj, SymbolNode) and obj is not SymbolNode
27682788
}
27692789

27702790

mypy/semanal_pass3.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
251251
if (isinstance(s.lvalues[0], NameExpr) and s.lvalues[0].kind == MDEF
252252
and isinstance(s.lvalues[0].node, Var)):
253253
var = s.lvalues[0].node
254-
if var.info is not None and var.is_inferred and not var.is_classvar:
254+
if var.info and var.is_inferred and not var.is_classvar:
255255
for base in var.info.mro[1:]:
256256
tnode = base.names.get(var.name())
257257
if (tnode is not None and isinstance(tnode.node, Var)

mypy/server/astmerge.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,8 @@ def process_type_info(self, info: Optional[TypeInfo]) -> None:
300300
self.fixup_type(info.tuple_type)
301301
self.fixup_type(info.typeddict_type)
302302
info.defn.info = self.fixup(info)
303-
info.replaced = self.fixup(info.replaced)
303+
if info.replaced:
304+
info.replaced = self.fixup(info.replaced)
304305
replace_nodes_in_symbol_table(info.names, self.replacements)
305306
for i, item in enumerate(info.mro):
306307
info.mro[i] = self.fixup(info.mro[i])

mypy/server/update.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1070,7 +1070,7 @@ def target_from_node(module: str,
10701070
return None
10711071
return module
10721072
elif isinstance(node, (OverloadedFuncDef, FuncDef)):
1073-
if node.info is not None:
1073+
if node.info:
10741074
return '%s.%s' % (node.info.fullname(), node.name())
10751075
else:
10761076
return '%s.%s' % (module, node.name())

mypy/typeanal.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,10 @@ def visit_callable_type(self, t: CallableType, nested: bool = True) -> Type:
413413
variables = self.bind_function_type_variables(t, t)
414414
ret = t.copy_modified(arg_types=self.anal_array(t.arg_types, nested=nested),
415415
ret_type=self.anal_type(t.ret_type, nested=nested),
416-
fallback=t.fallback or self.named_type('builtins.function'),
416+
# If the fallback isn't filled in yet,
417+
# its type will be the falsey FakeInfo
418+
fallback=(t.fallback if t.fallback.type
419+
else self.named_type('builtins.function')),
417420
variables=self.anal_var_defs(variables))
418421
return ret
419422

@@ -437,7 +440,9 @@ def visit_tuple_type(self, t: TupleType) -> Type:
437440
else:
438441
return AnyType(TypeOfAny.from_error)
439442
any_type = AnyType(TypeOfAny.special_form)
440-
fallback = t.fallback if t.fallback else self.named_type('builtins.tuple', [any_type])
443+
# If the fallback isn't filled in yet, its type will be the falsey FakeInfo
444+
fallback = (t.fallback if t.fallback.type
445+
else self.named_type('builtins.tuple', [any_type]))
441446
return TupleType(self.anal_array(t.items), fallback, t.line)
442447

443448
def visit_typeddict_type(self, t: TypedDictType) -> Type:

mypy/types.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -469,9 +469,7 @@ def deserialize(cls, data: JsonDict) -> 'DeletedType':
469469

470470

471471
# Fake TypeInfo to be used as a placeholder during Instance de-serialization.
472-
NOT_READY = mypy.nodes.FakeInfo(mypy.nodes.SymbolTable(),
473-
mypy.nodes.ClassDef('<NOT READY>', mypy.nodes.Block([])),
474-
'<NOT READY>')
472+
NOT_READY = mypy.nodes.FakeInfo('De-serialization failure: TypeInfo not fixed')
475473

476474

477475
class Instance(Type):
@@ -485,7 +483,7 @@ class Instance(Type):
485483
def __init__(self, typ: mypy.nodes.TypeInfo, args: List[Type],
486484
line: int = -1, column: int = -1, erased: bool = False) -> None:
487485
super().__init__(line, column)
488-
assert typ is NOT_READY or typ.fullname() not in ["builtins.Any", "typing.Any"]
486+
assert not typ or typ.fullname() not in ["builtins.Any", "typing.Any"]
489487
self.type = typ
490488
self.args = args
491489
self.erased = erased # True if result of type variable substitution

0 commit comments

Comments
 (0)