diff --git a/mypy/build.py b/mypy/build.py index b7ee2d09ae5e..679c331f0ddb 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -774,7 +774,7 @@ def is_meta_fresh(meta: Optional[CacheMeta], id: str, path: str, manager: BuildM st = manager.get_stat(path) # TODO: Errors if st.st_mtime != meta.mtime or st.st_size != meta.size: manager.log('Metadata abandoned for {}: file {} is modified'.format(id, path)) - return None + return False # It's a match on (id, path, mtime, size). # Check data_json; assume if its mtime matches it's good. @@ -1650,7 +1650,11 @@ def process_graph(graph: Graph, manager: BuildManager) -> None: process_stale_scc(graph, scc) sccs_left = len(fresh_scc_queue) - manager.log("{} fresh SCCs left in queue (and will remain unprocessed)".format(sccs_left)) + if sccs_left: + manager.log("{} fresh SCCs left in queue (and will remain unprocessed)".format(sccs_left)) + manager.trace(str(fresh_scc_queue)) + else: + manager.log("No fresh SCCs left in queue") def order_ascc(graph: Graph, ascc: AbstractSet[str], pri_max: int = PRI_ALL) -> List[str]: diff --git a/mypy/semanal.py b/mypy/semanal.py index 836e56b3a568..ea195b5fe5ca 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -913,11 +913,13 @@ def add_submodules_to_parent_modules(self, id: str, module_public: bool) -> None """ while '.' in id: parent, child = id.rsplit('.', 1) - modules_loaded = parent in self.modules and id in self.modules - if modules_loaded and child not in self.modules[parent].names: - sym = SymbolTableNode(MODULE_REF, self.modules[id], parent, - module_public=module_public) - self.modules[parent].names[child] = sym + parent_mod = self.modules.get(parent) + if parent_mod and child not in parent_mod.names: + child_mod = self.modules.get(id) + if child_mod: + sym = SymbolTableNode(MODULE_REF, child_mod, parent, + module_public=module_public) + parent_mod.names[child] = sym id = parent def add_module_symbol(self, id: str, as_id: str, module_public: bool, @@ -931,48 +933,47 @@ def add_module_symbol(self, id: str, as_id: str, module_public: bool, def visit_import_from(self, imp: ImportFrom) -> None: import_id = self.correct_relative_import(imp) - if import_id in self.modules: - module = self.modules[import_id] - self.add_submodules_to_parent_modules(import_id, True) - for id, as_id in imp.names: - node = module.names.get(id) - - # If the module does not contain a symbol with the name 'id', - # try checking if it's a module instead. - if id not in module.names or node.kind == UNBOUND_IMPORTED: - possible_module_id = import_id + '.' + id - mod = self.modules.get(possible_module_id) - if mod is not None: - node = SymbolTableNode(MODULE_REF, mod, import_id) - self.add_submodules_to_parent_modules(possible_module_id, True) - - if node and node.kind != UNBOUND_IMPORTED: - node = self.normalize_type_alias(node, imp) - if not node: - return - imported_id = as_id or id - existing_symbol = self.globals.get(imported_id) - if existing_symbol: - # Import can redefine a variable. They get special treatment. - if self.process_import_over_existing_name( - imported_id, existing_symbol, node, imp): - continue - # 'from m import x as x' exports x in a stub file. - module_public = not self.is_stub_file or as_id is not None - symbol = SymbolTableNode(node.kind, node.node, - self.cur_mod_id, - node.type_override, - module_public=module_public) - self.add_symbol(imported_id, symbol, imp) - else: - message = "Module '{}' has no attribute '{}'".format(import_id, id) - extra = self.undefined_name_extra_info('{}.{}'.format(import_id, id)) - if extra: - message += " {}".format(extra) - self.fail(message, imp) - else: - # Missing module. - for id, as_id in imp.names: + self.add_submodules_to_parent_modules(import_id, True) + module = self.modules.get(import_id) + for id, as_id in imp.names: + node = module.names.get(id) if module else None + + # If the module does not contain a symbol with the name 'id', + # try checking if it's a module instead. + if not node or node.kind == UNBOUND_IMPORTED: + possible_module_id = import_id + '.' + id + mod = self.modules.get(possible_module_id) + if mod is not None: + node = SymbolTableNode(MODULE_REF, mod, import_id) + self.add_submodules_to_parent_modules(possible_module_id, True) + + if node and node.kind != UNBOUND_IMPORTED: + node = self.normalize_type_alias(node, imp) + if not node: + return + imported_id = as_id or id + existing_symbol = self.globals.get(imported_id) + if existing_symbol: + # Import can redefine a variable. They get special treatment. + if self.process_import_over_existing_name( + imported_id, existing_symbol, node, imp): + continue + # 'from m import x as x' exports x in a stub file. + module_public = not self.is_stub_file or as_id is not None + symbol = SymbolTableNode(node.kind, node.node, + self.cur_mod_id, + node.type_override, + module_public=module_public) + self.add_symbol(imported_id, symbol, imp) + elif module: + # Missing attribute. + message = "Module '{}' has no attribute '{}'".format(import_id, id) + extra = self.undefined_name_extra_info('{}.{}'.format(import_id, id)) + if extra: + message += " {}".format(extra) + self.fail(message, imp) + else: + # Missing module. self.add_unknown_symbol(as_id or id, imp, is_import=True) def process_import_over_existing_name(self, diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 6dcd613b7453..2193bbb98d7b 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -1615,3 +1615,22 @@ from funcs import callee from classes import Outer def caller(a: Outer.Inner) -> int: callee(a) + +[case testIncrementalLoadsParentAfterChild] +# cmd: mypy -m r.s + +[file r/__init__.py] +from . import s + +[file r/m.py] +class R: pass + +[file r/s.py] +from . import m +R = m.R +a = None # type: R + +[file r/s.py.next] +from . import m +R = m.R +a = None # type: R