Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
7 changes: 7 additions & 0 deletions mypy/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,7 @@ def __init__(
self.options = options
self.version_id = version_id
self.modules: dict[str, MypyFile] = {}
self.import_map: dict[str, set[str]] = {}
self.missing_modules: set[str] = set()
self.fg_deps_meta: dict[str, FgDepMeta] = {}
# fg_deps holds the dependencies of every module that has been
Expand All @@ -623,6 +624,7 @@ def __init__(
self.incomplete_namespaces,
self.errors,
self.plugin,
self.import_map,
)
self.all_types: dict[Expression, Type] = {} # Enabled by export_types
self.indirection_detector = TypeIndirectionVisitor()
Expand Down Expand Up @@ -2898,6 +2900,11 @@ def dispatch(sources: list[BuildSource], manager: BuildManager, stdout: TextIO)
manager.cache_enabled = False
graph = load_graph(sources, manager)

for id in graph:
ancestors = graph[id].ancestors
assert ancestors is not None
manager.import_map[id] = set(graph[id].dependencies + graph[id].suppressed + ancestors)

t1 = time.time()
manager.add_stats(
graph_size=len(graph),
Expand Down
20 changes: 10 additions & 10 deletions mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5002,27 +5002,27 @@ def local_definitions(
SYMBOL_TABLE_NODE: Final[Tag] = 61


def read_symbol(data: Buffer) -> mypy.nodes.SymbolNode:
def read_symbol(data: Buffer) -> SymbolNode:
tag = read_tag(data)
# The branches here are ordered manually by type "popularity".
if tag == VAR:
return mypy.nodes.Var.read(data)
return Var.read(data)
if tag == FUNC_DEF:
return mypy.nodes.FuncDef.read(data)
return FuncDef.read(data)
if tag == DECORATOR:
return mypy.nodes.Decorator.read(data)
return Decorator.read(data)
if tag == TYPE_INFO:
return mypy.nodes.TypeInfo.read(data)
return TypeInfo.read(data)
if tag == OVERLOADED_FUNC_DEF:
return mypy.nodes.OverloadedFuncDef.read(data)
return OverloadedFuncDef.read(data)
if tag == TYPE_VAR_EXPR:
return mypy.nodes.TypeVarExpr.read(data)
return TypeVarExpr.read(data)
if tag == TYPE_ALIAS:
return mypy.nodes.TypeAlias.read(data)
return TypeAlias.read(data)
if tag == PARAM_SPEC_EXPR:
return mypy.nodes.ParamSpecExpr.read(data)
return ParamSpecExpr.read(data)
if tag == TYPE_VAR_TUPLE_EXPR:
return mypy.nodes.TypeVarTupleExpr.read(data)
return TypeVarTupleExpr.read(data)
assert False, f"Unknown symbol tag {tag}"


Expand Down
30 changes: 29 additions & 1 deletion mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ def __init__(
incomplete_namespaces: set[str],
errors: Errors,
plugin: Plugin,
import_map: dict[str, set[str]],
) -> None:
"""Construct semantic analyzer.

Expand Down Expand Up @@ -483,6 +484,7 @@ def __init__(
self.loop_depth = [0]
self.errors = errors
self.modules = modules
self.import_map = import_map
self.msg = MessageBuilder(errors, modules)
self.missing_modules = missing_modules
self.missing_names = [set()]
Expand Down Expand Up @@ -534,6 +536,9 @@ def __init__(
self.type_expression_full_parse_success_count: int = 0 # Successful full parses
self.type_expression_full_parse_failure_count: int = 0 # Failed full parses

# Imports of submodules transitively visible from given module.
self.transitive_submodule_imports: dict[str, set[str]] = {}

# mypyc doesn't properly handle implementing an abstractproperty
# with a regular attribute so we make them properties
@property
Expand Down Expand Up @@ -6637,7 +6642,7 @@ def get_module_symbol(self, node: MypyFile, name: str) -> SymbolTableNode | None
sym = names.get(name)
if not sym:
fullname = module + "." + name
if fullname in self.modules:
if fullname in self.modules and self.is_visible_import(fullname):
sym = SymbolTableNode(GDEF, self.modules[fullname])
elif self.is_incomplete_namespace(module):
self.record_incomplete_ref()
Expand All @@ -6656,6 +6661,29 @@ def get_module_symbol(self, node: MypyFile, name: str) -> SymbolTableNode | None
sym = None
return sym

def is_visible_import(self, id: str) -> bool:
if self.cur_mod_id not in self.transitive_submodule_imports:
self.add_transitive_submodule_imports(self.cur_mod_id)
return id in self.transitive_submodule_imports[self.cur_mod_id]

def add_transitive_submodule_imports(self, mod_id: str) -> None:
todo = self.import_map[mod_id]
seen = {mod_id}
result = {mod_id}
while todo:
dep = todo.pop()
if dep in seen:
continue
seen.add(dep)
if "." in dep:
result.add(dep)
if dep in self.transitive_submodule_imports:
result |= self.transitive_submodule_imports[dep]
continue
if dep in self.import_map:
todo |= self.import_map[dep]
self.transitive_submodule_imports[mod_id] = result

def is_missing_module(self, module: str) -> bool:
return module in self.missing_modules

Expand Down
47 changes: 47 additions & 0 deletions test-data/unit/check-incremental.test
Original file line number Diff line number Diff line change
Expand Up @@ -7512,3 +7512,50 @@ tmp/impl.py:31: note: Revealed type is "builtins.object"
tmp/impl.py:32: note: Revealed type is "Union[builtins.int, builtins.str, lib.Unrelated]"
tmp/impl.py:33: note: Revealed type is "builtins.object"
tmp/impl.py:34: note: Revealed type is "builtins.object"

[case testIncrementalAccessSubmoduleWithoutExplicitImport]
import b
import a

[file a.py]
import pkg

pkg.submod.foo()

[file a.py.2]
import pkg

pkg.submod.foo()
x = 1

[file b.py]
import c

[file c.py]
from pkg import submod

[file pkg/__init__.pyi]
[file pkg/submod.pyi]
def foo() -> None: pass
[out]
tmp/a.py:3: error: "object" has no attribute "submod"
[out2]
tmp/a.py:3: error: "object" has no attribute "submod"

[case testIncrementalAccessSubmoduleWithoutExplicitImportNested]
import pandas

pandas.core.dtypes

[file pandas/__init__.py]
import pandas.core.api

[file pandas/core/__init__.py]
[file pandas/core/api.py]
from pandas.core.dtypes.dtypes import X

[file pandas/core/dtypes/__init__.py]
[file pandas/core/dtypes/dtypes.py]
X = 0
[out]
[out2]