Skip to content

Commit 438f505

Browse files
committed
Remove the patch parent phase
This commit removes the "patch_parent" phase. The logic within those phases are now run only when a file is actually importing something, instead of being done automatically. This fixes some irregularities with how imports are handled. In particular, the "patch parent" stage was interacting poorly with implicit module references since the automatic patch would add back in an explicit module reference even when it was removed, which could preserve an implicit reference that should have instead been broken. That is, the presense of "patch parent" was sometimes concealing errors that should have been caught.
1 parent 140161d commit 438f505

File tree

2 files changed

+27
-17
lines changed

2 files changed

+27
-17
lines changed

mypy/build.py

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1399,19 +1399,6 @@ def parse_file(self) -> None:
13991399
self.dep_line_map = dep_line_map
14001400
self.check_blockers()
14011401

1402-
def patch_parent(self) -> None:
1403-
# Include module in the symbol table of the enclosing package.
1404-
if '.' not in self.id:
1405-
return
1406-
manager = self.manager
1407-
modules = manager.modules
1408-
parent, child = self.id.rsplit('.', 1)
1409-
if parent in modules:
1410-
manager.trace("Added %s.%s" % (parent, child))
1411-
modules[parent].names[child] = SymbolTableNode(MODULE_REF, self.tree, parent)
1412-
else:
1413-
manager.log("Hm... couldn't add %s.%s" % (parent, child))
1414-
14151402
def semantic_analysis(self) -> None:
14161403
with self.wrap_context():
14171404
self.manager.semantic_analyzer.visit_file(self.tree, self.xpath)
@@ -1682,8 +1669,6 @@ def process_fresh_scc(graph: Graph, scc: List[str]) -> None:
16821669
"""Process the modules in one SCC from their cached data."""
16831670
for id in scc:
16841671
graph[id].load_tree()
1685-
for id in scc:
1686-
graph[id].patch_parent()
16871672
for id in scc:
16881673
graph[id].fix_cross_refs()
16891674
for id in scc:
@@ -1697,8 +1682,6 @@ def process_stale_scc(graph: Graph, scc: List[str]) -> None:
16971682
# If the former, parse_file() is a no-op.
16981683
graph[id].parse_file()
16991684
graph[id].fix_suppressed_dependencies(graph)
1700-
for id in scc:
1701-
graph[id].patch_parent()
17021685
for id in scc:
17031686
graph[id].semantic_analysis()
17041687
for id in scc:

mypy/semanal.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -894,6 +894,24 @@ def visit_import(self, i: Import) -> None:
894894
base = id.split('.')[0]
895895
self.add_module_symbol(base, base, module_public=module_public,
896896
context=i)
897+
self.add_parent_modules(id, module_public)
898+
899+
def add_parent_modules(self, id: str, module_public: bool) -> None:
900+
"""Adds all parent modules to the symbol table given a fully qualified import.
901+
902+
It turns out that when you have imports of the form `import a.b.c`, Python will
903+
actually import not only module `a.b.c`, but modules `a.b` and `a` as well. This
904+
method adds in those checks.
905+
906+
Note that this does NOT happen when you have imports of the form
907+
`import a.b.c as foo` -- here, only module `a.b.c` is loaded under the name `foo`."""
908+
while '.' in id:
909+
parent, child = id.rsplit('.', 1)
910+
if parent in self.modules and id in self.modules:
911+
sym = SymbolTableNode(MODULE_REF, self.modules[id], parent,
912+
module_public=module_public)
913+
self.modules[parent].names[child] = sym
914+
id = parent
897915

898916
def add_module_symbol(self, id: str, as_id: str, module_public: bool,
899917
context: Context) -> None:
@@ -910,6 +928,15 @@ def visit_import_from(self, imp: ImportFrom) -> None:
910928
module = self.modules[import_id]
911929
for id, as_id in imp.names:
912930
node = module.names.get(id)
931+
932+
# If the module does not contain a symbol with the name 'id',
933+
# try checking if it's a module instead.
934+
if id not in module.names or node.kind == UNBOUND_IMPORTED:
935+
possible_module_id = import_id + '.' + id
936+
mod = self.modules.get(possible_module_id)
937+
if mod is not None:
938+
node = SymbolTableNode(MODULE_REF, mod, import_id)
939+
913940
if node and node.kind != UNBOUND_IMPORTED:
914941
node = self.normalize_type_alias(node, imp)
915942
if not node:

0 commit comments

Comments
 (0)