From 1e0c5de91682062eea3ded5a5b4609f8a91080b1 Mon Sep 17 00:00:00 2001 From: Charlie Denton Date: Tue, 2 Jan 2024 11:59:02 +0000 Subject: [PATCH] Don't remove modules with descendants Modules are removed from the fine-grained build if they have not been "seen". This change ensures that we don't remove ancestor modules if their descendants have been seen. --- mypy/dmypy_server.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/mypy/dmypy_server.py b/mypy/dmypy_server.py index b4c3fe8fe0dcb..0e4ea4d68431a 100644 --- a/mypy/dmypy_server.py +++ b/mypy/dmypy_server.py @@ -684,10 +684,11 @@ def refresh_file(module: str, path: str) -> list[str]: # Find all original modules in graph that were not reached -- they are deleted. to_delete = [] + seen_and_ancestors = self._seen_and_ancestors(seen) for module_id in orig_modules: if module_id not in graph: continue - if module_id not in seen: + if module_id not in seen_and_ancestors: module_path = graph[module_id].path assert module_path is not None to_delete.append((module_id, module_path)) @@ -715,6 +716,29 @@ def refresh_file(module: str, path: str) -> list[str]: return messages + def _seen_and_ancestors(self, seen: set[str]) -> set[str]: + """Return the set of seen modules along with any ancestors not already in the set. + + For example, given this set: + + {"foo", "foo.bar", "a.b.c"} + + ... we would expect this set to be returned: + + {"foo", "foo.bar", "a.b.c", "a.b", "a"} + + This is used to stop us from deleting ancestor modules from the graph + when their descendants have been seen. + """ + seen_paths = seen.copy() + for module_path in seen: + while module_path := module_path.rpartition('.')[0]: + if module_path in seen_paths: + break + else: + seen_paths.add(module_path) + return seen_paths + def find_reachable_changed_modules( self, roots: list[BuildSource],