From e98564e63018e2a73674d5858b82e4c13495d3f6 Mon Sep 17 00:00:00 2001 From: inchang-ing <197932532+inchang-ing@users.noreply.github.com> Date: Sat, 24 Oct 2026 16:55:06 +0800 Subject: [PATCH] Restore cached dependency order after suppress/add round trips A dependency that is suppressed during graph loading (the importing module cannot resolve it) and up-suppressed afterwards (another module resolved it) is re-added by appending it to the end of the dependency list. The order-sensitive freshness check in State.is_fresh() then sees a changed list and marks the module stale on every run, even though the dependency set is identical to the cached one. After the up-suppress pass, restore the cached order when the current dependency set is unchanged; a genuine dependency change keeps the new order and the module is still rechecked. Reproducer (from the issue): site/ns/sub/__init__.py (namespace package, no top-level __init__) a.py: import ns.missing (missing submodule -> suppressed) b.py: import ns.sub e.py: import b PYTHONPATH=site mypy -v a.py e.py # run twice On the second run, 'Scheduling SCC singleton (a) as inherently stale' was always logged: a's cached dependency order is [os, builtins, ns, abc, typing], but the suppress/add round trip appends ns at the end. With this change the second run reports no stale SCCs. The second run also used to lose the 'Skipping analyzing "ns"' error that the first (and non-incremental) run reports, because the spurious recheck resolved ns differently; restoring the order fixes that output inconsistency as well. Fixes #22159. --- mypy/build.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/mypy/build.py b/mypy/build.py index a55fe4eb92d5..45b361e6a1b0 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -4458,6 +4458,15 @@ def load_graph( if dep in graph: st.add_dependency(dep) manager.missing_modules.pop(dep, None) + # Suppressing a dependency and adding it back appends it to the end of + # the list, so a module whose dependency list order changed only due to + # that round trip would be considered stale by the order-sensitive + # freshness check (State.is_fresh), even though nothing actually + # changed. Restore the cached order when the dependency set is + # unchanged; a genuine change keeps the new order and the module is + # (correctly) rechecked. + if st.meta is not None and st.dependencies_set == set(st.meta.dependencies): + st.dependencies = list(st.meta.dependencies) # Second, in the initial loop we skip indirect dependencies, so to make indirect dependencies # behave more consistently with regular ones, we suppress them manually here (when needed). for st in graph.values():