Skip to content

Commit 30c46ab

Browse files
authored
Properly track module_hidden and module_public for incomplete symbols (#8450)
This fixes some crash bugs involving import * from an import cycle.
1 parent ef0b0df commit 30c46ab

File tree

5 files changed

+71
-15
lines changed

5 files changed

+71
-15
lines changed

mypy/fixup.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
def fixup_module(tree: MypyFile, modules: Dict[str, MypyFile],
2323
allow_missing: bool) -> None:
2424
node_fixer = NodeFixer(modules, allow_missing)
25-
node_fixer.visit_symbol_table(tree.names)
25+
node_fixer.visit_symbol_table(tree.names, tree.fullname)
2626

2727

2828
# TODO: Fix up .info when deserializing, i.e. much earlier.
@@ -42,7 +42,7 @@ def visit_type_info(self, info: TypeInfo) -> None:
4242
if info.defn:
4343
info.defn.accept(self)
4444
if info.names:
45-
self.visit_symbol_table(info.names)
45+
self.visit_symbol_table(info.names, info.fullname)
4646
if info.bases:
4747
for base in info.bases:
4848
base.accept(self.type_fixer)
@@ -64,7 +64,7 @@ def visit_type_info(self, info: TypeInfo) -> None:
6464
self.current_info = save_info
6565

6666
# NOTE: This method *definitely* isn't part of the NodeVisitor API.
67-
def visit_symbol_table(self, symtab: SymbolTable) -> None:
67+
def visit_symbol_table(self, symtab: SymbolTable, table_fullname: str) -> None:
6868
# Copy the items because we may mutate symtab.
6969
for key, value in list(symtab.items()):
7070
cross_ref = value.cross_ref
@@ -76,7 +76,7 @@ def visit_symbol_table(self, symtab: SymbolTable) -> None:
7676
stnode = lookup_qualified_stnode(self.modules, cross_ref,
7777
self.allow_missing)
7878
if stnode is not None:
79-
assert stnode.node is not None
79+
assert stnode.node is not None, (table_fullname + "." + key, cross_ref)
8080
value.node = stnode.node
8181
elif not self.allow_missing:
8282
assert False, "Could not find cross-ref %s" % (cross_ref,)

mypy/nodes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3030,6 +3030,7 @@ def serialize(self, prefix: str, name: str) -> JsonDict:
30303030
and fullname != prefix + '.' + name
30313031
and not (isinstance(self.node, Var)
30323032
and self.node.from_module_getattr)):
3033+
assert not isinstance(self.node, PlaceholderNode)
30333034
data['cross_ref'] = fullname
30343035
return data
30353036
data['node'] = self.node.serialize()

mypy/semanal.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1742,13 +1742,25 @@ def process_imported_symbol(self,
17421742
fullname: str,
17431743
context: ImportBase) -> None:
17441744
imported_id = as_id or id
1745+
# 'from m import x as x' exports x in a stub file or when implicit
1746+
# re-exports are disabled.
1747+
module_public = (
1748+
not self.is_stub_file
1749+
and self.options.implicit_reexport
1750+
or as_id is not None
1751+
)
1752+
module_hidden = not module_public and fullname not in self.modules
1753+
17451754
if isinstance(node.node, PlaceholderNode):
17461755
if self.final_iteration:
17471756
self.report_missing_module_attribute(module_id, id, imported_id, context)
17481757
return
17491758
else:
17501759
# This might become a type.
1751-
self.mark_incomplete(imported_id, node.node, becomes_typeinfo=True)
1760+
self.mark_incomplete(imported_id, node.node,
1761+
module_public=module_public,
1762+
module_hidden=module_hidden,
1763+
becomes_typeinfo=True)
17521764
existing_symbol = self.globals.get(imported_id)
17531765
if (existing_symbol and not isinstance(existing_symbol.node, PlaceholderNode) and
17541766
not isinstance(node.node, PlaceholderNode)):
@@ -1760,14 +1772,6 @@ def process_imported_symbol(self,
17601772
# Imports are special, some redefinitions are allowed, so wait until
17611773
# we know what is the new symbol node.
17621774
return
1763-
# 'from m import x as x' exports x in a stub file or when implicit
1764-
# re-exports are disabled.
1765-
module_public = (
1766-
not self.is_stub_file
1767-
and self.options.implicit_reexport
1768-
or as_id is not None
1769-
)
1770-
module_hidden = not module_public and fullname not in self.modules
17711775
# NOTE: we take the original node even for final `Var`s. This is to support
17721776
# a common pattern when constants are re-exported (same applies to import *).
17731777
self.add_imported_symbol(imported_id, node, context,
@@ -1866,6 +1870,7 @@ def visit_import_all(self, i: ImportAll) -> None:
18661870
self.add_imported_symbol(name, node, i,
18671871
module_public=module_public,
18681872
module_hidden=not module_public)
1873+
18691874
else:
18701875
# Don't add any dummy symbols for 'from x import *' if 'x' is unknown.
18711876
pass
@@ -4338,6 +4343,7 @@ def add_imported_symbol(self,
43384343
module_public: bool = True,
43394344
module_hidden: bool = False) -> None:
43404345
"""Add an alias to an existing symbol through import."""
4346+
assert not module_hidden or not module_public
43414347
symbol = SymbolTableNode(node.kind, node.node,
43424348
module_public=module_public,
43434349
module_hidden=module_hidden)
@@ -4421,7 +4427,9 @@ def record_incomplete_ref(self) -> None:
44214427
self.num_incomplete_refs += 1
44224428

44234429
def mark_incomplete(self, name: str, node: Node,
4424-
becomes_typeinfo: bool = False) -> None:
4430+
becomes_typeinfo: bool = False,
4431+
module_public: bool = True,
4432+
module_hidden: bool = False) -> None:
44254433
"""Mark a definition as incomplete (and defer current analysis target).
44264434
44274435
Also potentially mark the current namespace as incomplete.
@@ -4440,7 +4448,9 @@ def mark_incomplete(self, name: str, node: Node,
44404448
assert self.statement
44414449
placeholder = PlaceholderNode(fullname, node, self.statement.line,
44424450
becomes_typeinfo=becomes_typeinfo)
4443-
self.add_symbol(name, placeholder, context=dummy_context())
4451+
self.add_symbol(name, placeholder,
4452+
module_public=module_public, module_hidden=module_hidden,
4453+
context=dummy_context())
44444454
self.missing_names.add(name)
44454455

44464456
def is_incomplete_namespace(self, fullname: str) -> bool:

test-data/unit/check-incremental.test

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5361,3 +5361,26 @@ reveal_type(z)
53615361
tmp/c.py:2: note: Revealed type is 'a.A'
53625362
[out2]
53635363
tmp/c.py:2: note: Revealed type is 'a.<subclass of "A" and "B">'
5364+
5365+
[case testStubFixupIssues]
5366+
import a
5367+
[file a.py]
5368+
import p
5369+
[file a.py.2]
5370+
import p
5371+
p.N
5372+
5373+
[file p/__init__.pyi]
5374+
from p.util import *
5375+
5376+
[file p/util.pyi]
5377+
from p.params import N
5378+
class Test: ...
5379+
x: N
5380+
5381+
[file p/params.pyi]
5382+
import p.util
5383+
class N(p.util.Test):
5384+
...
5385+
[out2]
5386+
tmp/a.py:2: error: "object" has no attribute "N"

test-data/unit/fine-grained.test

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9572,3 +9572,25 @@ c.py:2: note: Revealed type is 'a.A'
95729572
==
95739573
c.py:2: note: Revealed type is 'a.<subclass of "A" and "B">'
95749574

9575+
[case testStubFixupIssues]
9576+
[file a.py]
9577+
import p
9578+
[file a.py.2]
9579+
import p
9580+
# a change
9581+
9582+
[file p/__init__.pyi]
9583+
from p.util import *
9584+
9585+
[file p/util.pyi]
9586+
from p.params import N
9587+
class Test: ...
9588+
9589+
[file p/params.pyi]
9590+
import p.util
9591+
class N(p.util.Test):
9592+
...
9593+
9594+
[builtins fixtures/list.pyi]
9595+
[out]
9596+
==

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy