Skip to content

Commit c294efa

Browse files
authored
Fine-grained: Compare symbol table snapshots when following dependencies (#4598)
Previously we just compared symbol table node types, which would fail to propagate certain changes. For example, we could fail to detect a change from a module reference to a normal variable. This only affects detecting changes when we are propagating changes using fine-grained dependencies. Snapshot comparison was already being used when processing an entire module. Fixes #4595.
1 parent 3545a71 commit c294efa

File tree

3 files changed

+47
-41
lines changed

3 files changed

+47
-41
lines changed

mypy/server/astdiff.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ def snapshot_symbol_table(name_prefix: str, table: SymbolTable) -> Dict[str, Sna
244244
245245
Only "shallow" state is included in the snapshot -- references to
246246
things defined in other modules are represented just by the names of
247-
the targers.
247+
the targets.
248248
"""
249249
result = {} # type: Dict[str, SnapshotItem]
250250
for name, symbol in table.items():

mypy/server/update.py

Lines changed: 6 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,7 @@ def reprocess_nodes(manager: BuildManager,
817817
file_node = manager.modules[module_id]
818818
old_symbols = find_symbol_tables_recursive(file_node.fullname(), file_node.names)
819819
old_symbols = {name: names.copy() for name, names in old_symbols.items()}
820+
old_symbols_snapshot = snapshot_symbol_table(file_node.fullname(), file_node.names)
820821

821822
def key(node: DeferredNode) -> int:
822823
# Unlike modules which are sorted by name within SCC,
@@ -829,9 +830,6 @@ def key(node: DeferredNode) -> int:
829830
# TODO: ignore_all argument to set_file_ignored_lines
830831
manager.errors.set_file_ignored_lines(file_node.path, file_node.ignored_lines)
831832

832-
# Keep track of potentially affected attribute types before type checking.
833-
old_types_map = get_enclosing_namespace_types(nodes)
834-
835833
# Strip semantic analysis information.
836834
for deferred in nodes:
837835
strip_target(deferred.node)
@@ -878,8 +876,12 @@ def key(node: DeferredNode) -> int:
878876
if graph[module_id].type_checker().check_second_pass():
879877
more = True
880878

879+
new_symbols_snapshot = snapshot_symbol_table(file_node.fullname(), file_node.names)
881880
# Check if any attribute types were changed and need to be propagated further.
882-
new_triggered = get_triggered_namespace_items(old_types_map)
881+
changed = compare_symbol_table_snapshots(file_node.fullname(),
882+
old_symbols_snapshot,
883+
new_symbols_snapshot)
884+
new_triggered = {make_trigger(name) for name in changed}
883885

884886
# Dependencies may have changed.
885887
update_deps(module_id, nodes, graph, deps, manager.options)
@@ -909,40 +911,6 @@ def find_symbol_tables_recursive(prefix: str, symbols: SymbolTable) -> Dict[str,
909911
return result
910912

911913

912-
NamespaceNode = Union[TypeInfo, MypyFile]
913-
914-
915-
def get_enclosing_namespace_types(nodes: List[DeferredNode]) -> Dict[NamespaceNode,
916-
Dict[str, Type]]:
917-
types = {} # type: Dict[NamespaceNode, Dict[str, Type]]
918-
for deferred in nodes:
919-
info = deferred.active_typeinfo
920-
if info:
921-
target = info # type: Optional[NamespaceNode]
922-
elif isinstance(deferred.node, MypyFile):
923-
target = deferred.node
924-
else:
925-
target = None
926-
if target and target not in types:
927-
local_types = {name: node.node.type
928-
for name, node in target.names.items()
929-
if isinstance(node.node, Var) and node.node.type}
930-
types[target] = local_types
931-
return types
932-
933-
934-
def get_triggered_namespace_items(old_types_map: Dict[NamespaceNode, Dict[str, Type]]) -> Set[str]:
935-
new_triggered = set()
936-
for namespace_node, old_types in old_types_map.items():
937-
for name, node in namespace_node.names.items():
938-
if (name in old_types and
939-
(not isinstance(node.node, Var) or
940-
node.node.type and not is_identical_type(node.node.type, old_types[name]))):
941-
# Type checking a method changed an attribute type.
942-
new_triggered.add(make_trigger('{}.{}'.format(namespace_node.fullname(), name)))
943-
return new_triggered
944-
945-
946914
def update_deps(module_id: str,
947915
nodes: List[DeferredNode],
948916
graph: Dict[str, State],

test-data/unit/fine-grained-modules.test

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
-- Test cases for fine-grained incremental mode related to modules
22
--
3-
-- Covers adding and deleting modules, changes to multiple modules, and
4-
-- changes to import graph.
3+
-- Covers adding and deleting modules, changes to multiple modules,
4+
-- changes to import graph, and changes to module references.
55
--
66
-- The comments in fine-grained.test explain how these tests work.
77

@@ -808,3 +808,41 @@ def main() -> None:
808808
[file config.py]
809809
[out]
810810
==
811+
812+
813+
-- Misc
814+
-- ----
815+
816+
817+
[case testChangeModuleToVariable]
818+
from a import m
819+
m.x
820+
[file a.py]
821+
from b import m
822+
[file b.py]
823+
import m
824+
[file b.py.2]
825+
m = ''
826+
[file m.py]
827+
x = 1
828+
[file m2.py]
829+
[out]
830+
==
831+
main:2: error: "str" has no attribute "x"
832+
833+
[case testChangeVariableToModule]
834+
from a import m
835+
y: str = m
836+
[file a.py]
837+
from b import m
838+
[file b.py]
839+
m = ''
840+
[file b.py.2]
841+
import m
842+
[file m.py]
843+
x = 1
844+
[file m2.py]
845+
[builtins fixtures/module.pyi]
846+
[out]
847+
==
848+
main:2: error: Incompatible types in assignment (expression has type Module, variable has type "str")

0 commit comments

Comments
 (0)