Skip to content

Commit 6e05ce1

Browse files
committed
Produce mypyc errors when encountering bug #5423
Currently when we encounter #5423 (when someting is incorrectly inferred as None), we generate code that casts a variable to None incorrectly since we are confused about the real type. Now produce an error message saying to add an annotation when we detect it. Fix up the places in mypy this caused errors.
1 parent d4151a8 commit 6e05ce1

File tree

7 files changed

+24
-18
lines changed

7 files changed

+24
-18
lines changed

mypy/checker.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@
9494
'DeferredNode',
9595
[
9696
('node', DeferredNodeType),
97-
('context_type_name', Optional[str]), # Name of the surrounding class (for error messages)
9897
('active_typeinfo', Optional[TypeInfo]), # And its TypeInfo (for semantic analysis
9998
# self type handling)
10099
])
@@ -105,7 +104,6 @@
105104
'FineGrainedDeferredNode',
106105
[
107106
('node', FineGrainedDeferredNodeType),
108-
('context_type_name', Optional[str]),
109107
('active_typeinfo', Optional[TypeInfo]),
110108
])
111109

@@ -329,7 +327,7 @@ def check_second_pass(self,
329327
assert not self.deferred_nodes
330328
self.deferred_nodes = []
331329
done = set() # type: Set[Union[DeferredNodeType, FineGrainedDeferredNodeType]]
332-
for node, type_name, active_typeinfo in todo:
330+
for node, active_typeinfo in todo:
333331
if node in done:
334332
continue
335333
# This is useful for debugging:
@@ -371,14 +369,10 @@ def defer_node(self, node: DeferredNodeType, enclosing_class: Optional[TypeInfo]
371369
enclosing_class: for methods, the class where the method is defined
372370
NOTE: this can't handle nested functions/methods.
373371
"""
374-
if self.errors.type_name:
375-
type_name = self.errors.type_name[-1]
376-
else:
377-
type_name = None
378372
# We don't freeze the entire scope since only top-level functions and methods
379373
# can be deferred. Only module/class level scope information is needed.
380374
# Module-level scope information is preserved in the TypeChecker instance.
381-
self.deferred_nodes.append(DeferredNode(node, type_name, enclosing_class))
375+
self.deferred_nodes.append(DeferredNode(node, enclosing_class))
382376

383377
def handle_cannot_determine_type(self, name: str, context: Context) -> None:
384378
node = self.scope.top_non_lambda_function()

mypy/errors.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,6 @@ def initialize(self) -> None:
172172
self.error_info_map = OrderedDict()
173173
self.flushed_files = set()
174174
self.import_ctx = []
175-
self.type_name = [None]
176175
self.function_or_member = [None]
177176
self.ignored_lines = OrderedDict()
178177
self.used_ignored_lines = defaultdict(set)
@@ -192,7 +191,6 @@ def copy(self) -> 'Errors':
192191
self.read_source)
193192
new.file = self.file
194193
new.import_ctx = self.import_ctx[:]
195-
new.type_name = self.type_name[:]
196194
new.function_or_member = self.function_or_member[:]
197195
new.target_module = self.target_module
198196
new.scope = self.scope

mypy/fastparse.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ def do_func_def(self, n: Union[ast3.FunctionDef, ast3.AsyncFunctionDef],
580580
# Before 3.8, [typed_]ast the line number points to the first decorator.
581581
# In 3.8, it points to the 'def' line, where we want it.
582582
lineno += len(n.decorator_list)
583-
end_lineno = None
583+
end_lineno = None # type: Optional[int]
584584
else:
585585
# Set end_lineno to the old pre-3.8 lineno, in order to keep
586586
# existing "# type: ignore" comments working:

mypy/fixup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def visit_symbol_table(self, symtab: SymbolTable) -> None:
8383
assert stnode.node is not None
8484
value.node = stnode.node
8585
elif not self.allow_missing:
86-
assert stnode is not None, "Could not find cross-ref %s" % (cross_ref,)
86+
assert False, "Could not find cross-ref %s" % (cross_ref,)
8787
else:
8888
# We have a missing crossref in allow missing mode, need to put something
8989
value.node = missing_info(self.modules)

mypy/server/update.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,11 +1028,9 @@ def not_found() -> None:
10281028
node = modules[module] # type: Optional[SymbolNode]
10291029
file = None # type: Optional[MypyFile]
10301030
active_class = None
1031-
active_class_name = None
10321031
for c in components:
10331032
if isinstance(node, TypeInfo):
10341033
active_class = node
1035-
active_class_name = node.name()
10361034
if isinstance(node, MypyFile):
10371035
file = node
10381036
if (not isinstance(node, (MypyFile, TypeInfo))
@@ -1057,7 +1055,7 @@ def not_found() -> None:
10571055
# a deserialized TypeInfo with missing attributes.
10581056
not_found()
10591057
return [], None
1060-
result = [FineGrainedDeferredNode(file, None, None)]
1058+
result = [FineGrainedDeferredNode(file, None)]
10611059
stale_info = None # type: Optional[TypeInfo]
10621060
if node.is_protocol:
10631061
stale_info = node
@@ -1082,7 +1080,7 @@ def not_found() -> None:
10821080
# context will be wrong and it could be a partially initialized deserialized node.
10831081
not_found()
10841082
return [], None
1085-
return [FineGrainedDeferredNode(node, active_class_name, active_class)], None
1083+
return [FineGrainedDeferredNode(node, active_class)], None
10861084

10871085

10881086
def is_verbose(manager: BuildManager) -> bool:

mypyc/genops.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2123,7 +2123,7 @@ def visit_operator_assignment_stmt(self, stmt: OperatorAssignmentStmt) -> None:
21232123
self.assign(target, res, res.line)
21242124

21252125
def get_assignment_target(self, lvalue: Lvalue,
2126-
line: Optional[int] = None) -> AssignmentTarget:
2126+
line: int = -1) -> AssignmentTarget:
21272127
if isinstance(lvalue, NameExpr):
21282128
# If we are visiting a decorator, then the SymbolNode we really want to be looking at
21292129
# is the function that is decorated, not the entire Decorator node itself.
@@ -2168,7 +2168,7 @@ def get_assignment_target(self, lvalue: Lvalue,
21682168
return AssignmentTargetAttr(obj, lvalue.name)
21692169
elif isinstance(lvalue, TupleExpr):
21702170
# Multiple assignment a, ..., b = e
2171-
star_idx = None
2171+
star_idx = None # type: Optional[int]
21722172
lvalues = []
21732173
for idx, item in enumerate(lvalue.items):
21742174
targ = self.get_assignment_target(item)
@@ -2812,6 +2812,15 @@ def visit_name_expr(self, expr: NameExpr) -> Value:
28122812
# Except for imports, that currently always happens in the global namespace.
28132813
if expr.kind == LDEF and not (isinstance(expr.node, Var)
28142814
and expr.node.is_suppressed_import):
2815+
# Try to detect and error when we hit the irritating mypy bug
2816+
# where a local variable is cast to None. (#5423)
2817+
if (isinstance(expr.node, Var) and is_none_rprimitive(self.node_type(expr))
2818+
and expr.node.is_inferred):
2819+
self.error(
2820+
"Local variable '{}' has inferred type None; add an annotation".format(
2821+
expr.node.name()),
2822+
expr.node.line)
2823+
28152824
# TODO: Behavior currently only defined for Var and FuncDef node types.
28162825
return self.read(self.get_assignment_target(expr), expr.line)
28172826

mypyc/test-data/commandline.test

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,10 @@ a_str = " ".join(str(i) for i in range(10))
159159
wtvr = next(i for i in range(10) if i == 5)
160160

161161
d1 = {1: 2}
162+
163+
# Make sure we can produce an error when we hit the awful None case
164+
def f(l: List[object]) -> None:
165+
x = None # E: Local variable 'x' has inferred type None; add an annotation
166+
for i in l:
167+
if x is None:
168+
x = i

0 commit comments

Comments
 (0)