From 3005fc1239d459d0dd175fe890c42e9d47d734af Mon Sep 17 00:00:00 2001 From: Drew T <50529377+Druthulu@users.noreply.github.com> Date: Thu, 3 Sep 2026 20:38:59 -0600 Subject: [PATCH] fix(sync_tu_decls): a no-op sync is not progress, and a repeated symbol is not a blocker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit replace_decl returned True whenever the PATTERN matched, even when the substitution produced identical text. So a draft that already carries the TU's exact spelling looped until --rounds ran out, spending ONE CLEAN REBUILD PER ROUND, and then printed 'gave up after 6 rounds (6 synced)' — which reads as six useful syncs. Measured on func_8005FA94: 6 rounds, every one 'D_80072960 -> extern void (*D_80072960)(void *);', zero change to the draft, five wasted rebuilds and a misleading report. R61(a): a no-op must not be reported as work. Two guards: no text change ends the loop naming the already-correct spelling and saying the residual is elsewhere; and a symbol the gate names twice in one run ends it too, since re-syncing it cannot help. The comparison is LINE-NORMALISED because the pattern ends in \s*$ and the substitution eats the matched line's newline — a byte compare called that a change. Caught by a known-true check (identical/different/absent), not by reading the code. --- tools/sync_tu_decls.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/tools/sync_tu_decls.py b/tools/sync_tu_decls.py index 1380208e9..774717781 100644 --- a/tools/sync_tu_decls.py +++ b/tools/sync_tu_decls.py @@ -81,7 +81,18 @@ def replace_decl(text, sym, decl): pat = re.compile(r"^\s*extern\b[^;\n]*\b%s\b[^;\n]*;\s*$" % re.escape(sym), re.M) if not pat.search(text): return text, False - return pat.sub(decl, text, count=1), True + out = pat.sub(decl, text, count=1) + # NO TEXT CHANGE IS NO PROGRESS (P31 S77). This returned True whenever the PATTERN matched, even + # when the substitution produced byte-identical text — so a draft that already carries the TU's + # exact spelling looped until --rounds ran out, spending ONE CLEAN REBUILD PER ROUND and then + # reporting "gave up after 6 rounds (6 synced)", which reads as six useful syncs. Measured on + # func_8005FA94: 6 rounds, all `D_80072960 -> extern void (*D_80072960)(void *);`, zero change, + # five wasted rebuilds. (R61(a): a no-op must not be reported as work.) + # Compare LINE-NORMALISED: `pat` ends in `\s*$`, so the substitution eats the matched line's + # trailing newline and a byte compare reports a change that is pure whitespace. (Caught by the + # known-true check, not by reading the code.) + _n = lambda x: "\n".join(l.rstrip() for l in x.splitlines()) + return out, _n(out) != _n(text) def main(): @@ -179,7 +190,13 @@ def main(): return 1 txt, changed = replace_decl(open(draft, errors="replace").read(), sym, decl) if not changed: - print("stopping: the draft has no declaration of %s to replace." % sym) + print("stopping: the draft's declaration of %s is ALREADY the TU's spelling (%s) — " + "syncing it again cannot change anything, so the residual is elsewhere (a body " + "mismatch, or a conflict this tool does not model)." % (sym, decl)) + return 1 + if sym in synced: + print("stopping: %s was already synced this run and the gate named it again — the " + "declaration is not the blocker." % sym) return 1 open(draft, "w").write(txt) synced.append(sym)