From 6e05ce1f61efcfefc46b73953e61d4ac900b538f Mon Sep 17 00:00:00 2001 From: "Michael J. Sullivan" Date: Fri, 6 Sep 2019 17:14:45 -0700 Subject: [PATCH] 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. --- mypy/checker.py | 10 ++-------- mypy/errors.py | 2 -- mypy/fastparse.py | 2 +- mypy/fixup.py | 2 +- mypy/server/update.py | 6 ++---- mypyc/genops.py | 13 +++++++++++-- mypyc/test-data/commandline.test | 7 +++++++ 7 files changed, 24 insertions(+), 18 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index b758af4ad5e62..4db6e905c2569 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -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) ]) @@ -105,7 +104,6 @@ 'FineGrainedDeferredNode', [ ('node', FineGrainedDeferredNodeType), - ('context_type_name', Optional[str]), ('active_typeinfo', Optional[TypeInfo]), ]) @@ -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: @@ -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() diff --git a/mypy/errors.py b/mypy/errors.py index 77cf3fabbc424..136611ccd7479 100644 --- a/mypy/errors.py +++ b/mypy/errors.py @@ -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) @@ -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 diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 27501bc92271a..4de55fc46c592 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -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: diff --git a/mypy/fixup.py b/mypy/fixup.py index 1f82873bd2dd9..0a89bae0757cb 100644 --- a/mypy/fixup.py +++ b/mypy/fixup.py @@ -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) diff --git a/mypy/server/update.py b/mypy/server/update.py index 1e35af4f82479..b0a8fc2092b60 100644 --- a/mypy/server/update.py +++ b/mypy/server/update.py @@ -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)) @@ -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 @@ -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: diff --git a/mypyc/genops.py b/mypyc/genops.py index 8e3185cf4649e..09528b77ee691 100644 --- a/mypyc/genops.py +++ b/mypyc/genops.py @@ -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. @@ -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) @@ -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) diff --git a/mypyc/test-data/commandline.test b/mypyc/test-data/commandline.test index 80edfe74466f5..899b49eb88e4b 100644 --- a/mypyc/test-data/commandline.test +++ b/mypyc/test-data/commandline.test @@ -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