Skip to content

Allow importing a submodule ahead of its parent module #2237

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 10, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions mypy/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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]:
Expand Down
95 changes: 48 additions & 47 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
19 changes: 19 additions & 0 deletions test-data/unit/check-incremental.test
Original file line number Diff line number Diff line change
Expand Up @@ -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