Skip to content
Open
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
7 changes: 7 additions & 0 deletions mypy/server/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,13 @@ def visit_member_expr(self, e: MemberExpr) -> None:
if e.kind is not None:
# Reference to a module attribute
self.process_global_ref_expr(e)
if isinstance(e.expr, RefExpr) and isinstance(e.expr.node, MypyFile):
# Also depend on the name as accessed through this module. The
# fullname above may point to the original definition (e.g. via
# a re-export using "from ... import *"), but we must also
# recheck if the name is removed from the accessed module's
# namespace.
self.add_dependency(make_trigger(e.expr.node.fullname + "." + e.name))
else:
# Reference to a non-module (or missing) attribute
if e.expr not in self.type_map:
Expand Down
49 changes: 28 additions & 21 deletions mypy/server/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -783,31 +783,37 @@ def calculate_active_triggers(
else:
snapshot2 = snapshot_symbol_table(id, new.names)
diff = compare_symbol_table_snapshots(id, snapshot1, snapshot2)
package_nesting_level = id.count(".")
for item in diff.copy():
if item.count(".") <= package_nesting_level + 1 and item.split(".")[-1] not in (
"__builtins__",
"__file__",
"__name__",
"__package__",
"__doc__",
):
# Activate catch-all wildcard trigger for top-level module changes (used for
# "from m import *"). This also gets triggered by changes to module-private
# entries, but as these unneeded dependencies only result in extra processing,
# it's a minor problem.
#
# TODO: Some __* names cause mistriggers. Fix the underlying issue instead of
# special casing them here.
diff.add(id + WILDCARD_TAG)
if item.count(".") > package_nesting_level + 1:
# These are for changes within classes, used by protocols.
diff.add(item.rsplit(".", 1)[0] + WILDCARD_TAG)

diff |= wildcard_triggers_for_changes(id, diff)
names |= diff
return {make_trigger(name) for name in names}


def wildcard_triggers_for_changes(module_id: str, diff: set[str]) -> set[str]:
"""Return catch-all wildcard triggers activated by a set of changed names."""
result: set[str] = set()
package_nesting_level = module_id.count(".")
for item in diff:
if item.count(".") <= package_nesting_level + 1 and item.split(".")[-1] not in (
"__builtins__",
"__file__",
"__name__",
"__package__",
"__doc__",
):
# Activate catch-all wildcard trigger for top-level module changes (used for
# "from m import *"). This also gets triggered by changes to module-private
# entries, but as these unneeded dependencies only result in extra processing,
# it's a minor problem.
#
# TODO: Some __* names cause mistriggers. Fix the underlying issue instead of
# special casing them here.
result.add(module_id + WILDCARD_TAG)
if item.count(".") > package_nesting_level + 1:
# These are for changes within classes, used by protocols.
result.add(item.rsplit(".", 1)[0] + WILDCARD_TAG)
return result


def replace_modules_with_new_variants(
manager: BuildManager,
graph: dict[str, State],
Expand Down Expand Up @@ -1044,6 +1050,7 @@ def key(node: FineGrainedDeferredNode) -> int:
changed = compare_symbol_table_snapshots(
file_node.fullname, old_symbols_snapshot, new_symbols_snapshot
)
changed |= wildcard_triggers_for_changes(module_id, changed)
new_triggered = {make_trigger(name) for name in changed}

# Dependencies may have changed.
Expand Down
13 changes: 13 additions & 0 deletions test-data/unit/deps.test
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,19 @@ x = 1
<n.x> -> m.f
<n> -> m, m.f

[case testAccessReexportedModuleAttribute]
import pkg
pkg.f()
[file pkg/__init__.py]
from pkg.sub import *
[file pkg/sub.py]
def f() -> None: pass
[out]
<pkg.f> -> m
<pkg.sub.f> -> m
<pkg.sub[wildcard]> -> pkg
<pkg> -> m

[case testImport]
import n
[file n.py]
Expand Down
131 changes: 131 additions & 0 deletions test-data/unit/fine-grained.test
Original file line number Diff line number Diff line change
Expand Up @@ -11839,3 +11839,134 @@ def bar() -> str: return "a"
main:2: note: Revealed type is "None | builtins.int"
==
main:2: note: Revealed type is "None | builtins.str"

[case testStarExportedNameDeleted1]
import pkg

pkg.loads()

[file pkg/__init__.pyi]
from .sub import *

[file pkg/sub.pyi]
def loads() -> None: ...

[file pkg/sub.pyi.2]
[out]
==
main:3: error: "object" has no attribute "loads"

[case testStarExportedNameDeleted2]
import pkg

pkg.C()

[file pkg/__init__.pyi]
from .sub import *

[file pkg/sub.pyi]
class C: pass

[file pkg/sub.pyi.2]
[out]
==
main:3: error: "object" has no attribute "C"

[case testStarExportedNameDeleted3]
from pkg import loads

loads()

[file pkg/__init__.pyi]
from .sub import *

[file pkg/sub.pyi]
def loads() -> None: ...

[file pkg/sub.pyi.2]
[out]
==
main:1: error: Module "pkg" has no attribute "loads"

[case testStarExportedNameDeleted4]
from pkg import sub

a = sub.x

[file pkg/__init__.pyi]
from .sub import *

[file pkg/sub.pyi]
x = 1

[file pkg/sub.pyi.2]
[out]
==
main:3: error: "object" has no attribute "x"

[case testStarExportedNameDeleted5]
from pkg import sub

x: sub.N = 1

[file pkg/__init__.pyi]
from .sub import *

[file pkg/sub.pyi]
N = int

[file pkg/sub.pyi.2]
[out]
==
main:3: error: Name "sub.N" is not defined

[case testStarExportedNameDeleted6]
from pkg import *

loads()

[file pkg/__init__.pyi]
from .sub import *

[file pkg/sub.pyi]
def loads() -> None: ...

[file pkg/sub.pyi.2]
[out]
==
main:3: error: Name "loads" is not defined

[case testStarExportedNameDeleted7]
from pkg import *

C()

[file pkg/__init__.pyi]
from .sub import *

[file pkg/sub.pyi]
class C: pass

[file pkg/sub.pyi.2]
[out]
==
main:3: error: Name "C" is not defined

[case testStarExportedNameDeleted8]
from a import *

C()

[file a.py]
from b import *

[file b.py]
from c import *

[file c.py]
class C: pass

[file c.py.2]
[out]
==
main:3: error: Name "C" is not defined
Loading