Skip to content

Commit

Permalink
Produce mypyc errors when encountering bug #5423 (#7482)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
msullivan committed Sep 9, 2019
1 parent 52c286f commit bcbd172
Show file tree
Hide file tree
Showing 7 changed files with 24 additions and 18 deletions.
10 changes: 2 additions & 8 deletions mypy/checker.py
Expand Up @@ -94,7 +94,6 @@
'DeferredNode',
[
('node', DeferredNodeType),
('context_type_name', Optional[str]), # Name of the surrounding class (for error messages)
('active_typeinfo', Optional[TypeInfo]), # And its TypeInfo (for semantic analysis
# self type handling)
])
Expand All @@ -105,7 +104,6 @@
'FineGrainedDeferredNode',
[
('node', FineGrainedDeferredNodeType),
('context_type_name', Optional[str]),
('active_typeinfo', Optional[TypeInfo]),
])

Expand Down Expand Up @@ -329,7 +327,7 @@ def check_second_pass(self,
assert not self.deferred_nodes
self.deferred_nodes = []
done = set() # type: Set[Union[DeferredNodeType, FineGrainedDeferredNodeType]]
for node, type_name, active_typeinfo in todo:
for node, active_typeinfo in todo:
if node in done:
continue
# This is useful for debugging:
Expand Down Expand Up @@ -371,14 +369,10 @@ def defer_node(self, node: DeferredNodeType, enclosing_class: Optional[TypeInfo]
enclosing_class: for methods, the class where the method is defined
NOTE: this can't handle nested functions/methods.
"""
if self.errors.type_name:
type_name = self.errors.type_name[-1]
else:
type_name = None
# We don't freeze the entire scope since only top-level functions and methods
# can be deferred. Only module/class level scope information is needed.
# Module-level scope information is preserved in the TypeChecker instance.
self.deferred_nodes.append(DeferredNode(node, type_name, enclosing_class))
self.deferred_nodes.append(DeferredNode(node, enclosing_class))

def handle_cannot_determine_type(self, name: str, context: Context) -> None:
node = self.scope.top_non_lambda_function()
Expand Down
2 changes: 0 additions & 2 deletions mypy/errors.py
Expand Up @@ -172,7 +172,6 @@ def initialize(self) -> None:
self.error_info_map = OrderedDict()
self.flushed_files = set()
self.import_ctx = []
self.type_name = [None]
self.function_or_member = [None]
self.ignored_lines = OrderedDict()
self.used_ignored_lines = defaultdict(set)
Expand All @@ -192,7 +191,6 @@ def copy(self) -> 'Errors':
self.read_source)
new.file = self.file
new.import_ctx = self.import_ctx[:]
new.type_name = self.type_name[:]
new.function_or_member = self.function_or_member[:]
new.target_module = self.target_module
new.scope = self.scope
Expand Down
2 changes: 1 addition & 1 deletion mypy/fastparse.py
Expand Up @@ -580,7 +580,7 @@ def do_func_def(self, n: Union[ast3.FunctionDef, ast3.AsyncFunctionDef],
# Before 3.8, [typed_]ast the line number points to the first decorator.
# In 3.8, it points to the 'def' line, where we want it.
lineno += len(n.decorator_list)
end_lineno = None
end_lineno = None # type: Optional[int]
else:
# Set end_lineno to the old pre-3.8 lineno, in order to keep
# existing "# type: ignore" comments working:
Expand Down
2 changes: 1 addition & 1 deletion mypy/fixup.py
Expand Up @@ -83,7 +83,7 @@ def visit_symbol_table(self, symtab: SymbolTable) -> None:
assert stnode.node is not None
value.node = stnode.node
elif not self.allow_missing:
assert stnode is not None, "Could not find cross-ref %s" % (cross_ref,)
assert False, "Could not find cross-ref %s" % (cross_ref,)
else:
# We have a missing crossref in allow missing mode, need to put something
value.node = missing_info(self.modules)
Expand Down
6 changes: 2 additions & 4 deletions mypy/server/update.py
Expand Up @@ -1028,11 +1028,9 @@ def not_found() -> None:
node = modules[module] # type: Optional[SymbolNode]
file = None # type: Optional[MypyFile]
active_class = None
active_class_name = None
for c in components:
if isinstance(node, TypeInfo):
active_class = node
active_class_name = node.name()
if isinstance(node, MypyFile):
file = node
if (not isinstance(node, (MypyFile, TypeInfo))
Expand All @@ -1057,7 +1055,7 @@ def not_found() -> None:
# a deserialized TypeInfo with missing attributes.
not_found()
return [], None
result = [FineGrainedDeferredNode(file, None, None)]
result = [FineGrainedDeferredNode(file, None)]
stale_info = None # type: Optional[TypeInfo]
if node.is_protocol:
stale_info = node
Expand All @@ -1082,7 +1080,7 @@ def not_found() -> None:
# context will be wrong and it could be a partially initialized deserialized node.
not_found()
return [], None
return [FineGrainedDeferredNode(node, active_class_name, active_class)], None
return [FineGrainedDeferredNode(node, active_class)], None


def is_verbose(manager: BuildManager) -> bool:
Expand Down
13 changes: 11 additions & 2 deletions mypyc/genops.py
Expand Up @@ -2123,7 +2123,7 @@ def visit_operator_assignment_stmt(self, stmt: OperatorAssignmentStmt) -> None:
self.assign(target, res, res.line)

def get_assignment_target(self, lvalue: Lvalue,
line: Optional[int] = None) -> AssignmentTarget:
line: int = -1) -> AssignmentTarget:
if isinstance(lvalue, NameExpr):
# If we are visiting a decorator, then the SymbolNode we really want to be looking at
# is the function that is decorated, not the entire Decorator node itself.
Expand Down Expand Up @@ -2168,7 +2168,7 @@ def get_assignment_target(self, lvalue: Lvalue,
return AssignmentTargetAttr(obj, lvalue.name)
elif isinstance(lvalue, TupleExpr):
# Multiple assignment a, ..., b = e
star_idx = None
star_idx = None # type: Optional[int]
lvalues = []
for idx, item in enumerate(lvalue.items):
targ = self.get_assignment_target(item)
Expand Down Expand Up @@ -2812,6 +2812,15 @@ def visit_name_expr(self, expr: NameExpr) -> Value:
# Except for imports, that currently always happens in the global namespace.
if expr.kind == LDEF and not (isinstance(expr.node, Var)
and expr.node.is_suppressed_import):
# Try to detect and error when we hit the irritating mypy bug
# where a local variable is cast to None. (#5423)
if (isinstance(expr.node, Var) and is_none_rprimitive(self.node_type(expr))
and expr.node.is_inferred):
self.error(
"Local variable '{}' has inferred type None; add an annotation".format(
expr.node.name()),
expr.node.line)

# TODO: Behavior currently only defined for Var and FuncDef node types.
return self.read(self.get_assignment_target(expr), expr.line)

Expand Down
7 changes: 7 additions & 0 deletions mypyc/test-data/commandline.test
Expand Up @@ -159,3 +159,10 @@ a_str = " ".join(str(i) for i in range(10))
wtvr = next(i for i in range(10) if i == 5)

d1 = {1: 2}

# Make sure we can produce an error when we hit the awful None case
def f(l: List[object]) -> None:
x = None # E: Local variable 'x' has inferred type None; add an annotation
for i in l:
if x is None:
x = i

0 comments on commit bcbd172

Please sign in to comment.