Skip to content

Fine-grained: Compare symbol table snapshots when following dependencies #4598

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 20, 2018
Merged
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
2 changes: 1 addition & 1 deletion mypy/server/astdiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ def snapshot_symbol_table(name_prefix: str, table: SymbolTable) -> Dict[str, Sna

Only "shallow" state is included in the snapshot -- references to
things defined in other modules are represented just by the names of
the targers.
the targets.
"""
result = {} # type: Dict[str, SnapshotItem]
for name, symbol in table.items():
Expand Down
44 changes: 6 additions & 38 deletions mypy/server/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,7 @@ def reprocess_nodes(manager: BuildManager,
file_node = manager.modules[module_id]
old_symbols = find_symbol_tables_recursive(file_node.fullname(), file_node.names)
old_symbols = {name: names.copy() for name, names in old_symbols.items()}
old_symbols_snapshot = snapshot_symbol_table(file_node.fullname(), file_node.names)

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

# Keep track of potentially affected attribute types before type checking.
old_types_map = get_enclosing_namespace_types(nodes)

# Strip semantic analysis information.
for deferred in nodes:
strip_target(deferred.node)
Expand Down Expand Up @@ -878,8 +876,12 @@ def key(node: DeferredNode) -> int:
if graph[module_id].type_checker().check_second_pass():
more = True

new_symbols_snapshot = snapshot_symbol_table(file_node.fullname(), file_node.names)
# Check if any attribute types were changed and need to be propagated further.
new_triggered = get_triggered_namespace_items(old_types_map)
changed = compare_symbol_table_snapshots(file_node.fullname(),
old_symbols_snapshot,
new_symbols_snapshot)
new_triggered = {make_trigger(name) for name in changed}

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


NamespaceNode = Union[TypeInfo, MypyFile]


def get_enclosing_namespace_types(nodes: List[DeferredNode]) -> Dict[NamespaceNode,
Dict[str, Type]]:
types = {} # type: Dict[NamespaceNode, Dict[str, Type]]
for deferred in nodes:
info = deferred.active_typeinfo
if info:
target = info # type: Optional[NamespaceNode]
elif isinstance(deferred.node, MypyFile):
target = deferred.node
else:
target = None
if target and target not in types:
local_types = {name: node.node.type
for name, node in target.names.items()
if isinstance(node.node, Var) and node.node.type}
types[target] = local_types
return types


def get_triggered_namespace_items(old_types_map: Dict[NamespaceNode, Dict[str, Type]]) -> Set[str]:
new_triggered = set()
for namespace_node, old_types in old_types_map.items():
for name, node in namespace_node.names.items():
if (name in old_types and
(not isinstance(node.node, Var) or
node.node.type and not is_identical_type(node.node.type, old_types[name]))):
# Type checking a method changed an attribute type.
new_triggered.add(make_trigger('{}.{}'.format(namespace_node.fullname(), name)))
return new_triggered


def update_deps(module_id: str,
nodes: List[DeferredNode],
graph: Dict[str, State],
Expand Down
42 changes: 40 additions & 2 deletions test-data/unit/fine-grained-modules.test
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
-- Test cases for fine-grained incremental mode related to modules
--
-- Covers adding and deleting modules, changes to multiple modules, and
-- changes to import graph.
-- Covers adding and deleting modules, changes to multiple modules,
-- changes to import graph, and changes to module references.
--
-- The comments in fine-grained.test explain how these tests work.

Expand Down Expand Up @@ -808,3 +808,41 @@ def main() -> None:
[file config.py]
[out]
==


-- Misc
-- ----


[case testChangeModuleToVariable]
from a import m
m.x
[file a.py]
from b import m
[file b.py]
import m
[file b.py.2]
m = ''
[file m.py]
x = 1
[file m2.py]
[out]
==
main:2: error: "str" has no attribute "x"

[case testChangeVariableToModule]
from a import m
y: str = m
[file a.py]
from b import m
[file b.py]
m = ''
[file b.py.2]
import m
[file m.py]
x = 1
[file m2.py]
[builtins fixtures/module.pyi]
[out]
==
main:2: error: Incompatible types in assignment (expression has type Module, variable has type "str")