From 39ac37a808d05505b37e8edb2a427fb792d45e78 Mon Sep 17 00:00:00 2001 From: Drew T <50529377+Druthulu@users.noreply.github.com> Date: Thu, 3 Sep 2026 20:20:56 -0600 Subject: [PATCH] fix(gate_main): the in-TU clash pre-check must only compare FILE-SCOPE declarations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DECL is `^\s*extern`/MULTILINE, so it matched an INDENTED extern inside a function body, and the pre-check then compared that block-scope declaration against the TU's file-scope spelling — making the checker STRICTER THAN CC1. Per cookbook §481, gcc-2.7.2 raises `conflicting types' as an ERROR only in the SAME scope; across scopes it degrades to `type mismatch with previous external decl', a WARNING the build already emits elsewhere. So a block-scope extern cannot clash, and dropping on one refuses correct work. Measured in a single gate: func_8001FC08 (400 ins — a deliberately renamed MTX_8001FC08 at block scope, which is the ONLY legal fix there because two anonymous struct typedefs in one TU are never compatible in C89) and func_8002FF0C (166 ins — a deliberate block-scope scalar shadow of D_800A46D2). 566 instructions of byte-correct body refused by a rule the compiler does not apply. _depth0() blanks brace-nested regions, string literals and comments before DECL runs, on both the TU side and the draft side. NEGATIVE CONTROL (R39): on a synthetic TU it keeps both file-scope decls and excludes the block-scope, in-string and in-comment ones; on the two real drafts it removes EXACTLY the three disputed symbols (D_80074818, D_80075018, D_800A46D2) and leaves all 23 other declarations in each untouched. R39 governs the direction: a check that discards good work is worse than one that lets a failure through, and a real conflict still surfaces via the COMPILE-conflict path plus a byte gate that cannot be fooled. R61(b). --- tools/gate_main.py | 49 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/tools/gate_main.py b/tools/gate_main.py index 9562edf3ee..7911d7e8ea 100644 --- a/tools/gate_main.py +++ b/tools/gate_main.py @@ -276,6 +276,51 @@ def live_text(txt): out.append('' if dead else ln) return '\n'.join(out) + +def _depth0(text): + r"""`text` with every brace-nested region blanked, so DECL only sees FILE-SCOPE declarations. + + A BLOCK-SCOPE `extern` CANNOT CLASH WITH THE TU's FILE-SCOPE ONE (P31 S77, cookbook §481). + gcc-2.7.2 raises `conflicting types' as an ERROR only in the SAME scope; across scopes it + degrades to `type mismatch with previous external decl' — a WARNING the build already emits + elsewhere. DECL is `^\s*extern`/MULTILINE, so it happily matched an INDENTED extern inside a + function body and this pre-check compared it against the TU's file-scope spelling — making the + checker STRICTER THAN CC1 and dropping byte-correct work. + + Measured, in one gate: `func_8001FC08` (400 ins — a deliberately renamed `MTX_8001FC08` at + block scope, the §481 escape hatch, the ONLY legal fix because two anonymous struct typedefs in + one TU are never compatible in C89) and `func_8002FF0C` (166 ins — a deliberate block-scope + scalar shadow of `D_800A46D2`). 566 instructions refused by a rule the compiler does not apply. + + R39 governs the direction of the error: a check that discards good work is worse than one that + lets a failure through, and a real conflict still surfaces as the COMPILE-conflict path plus a + byte gate that cannot be fooled.""" + out, depth, i, n = [], 0, 0, len(text) + while i < n: + c = text[i] + if c == '/' and i + 1 < n and text[i + 1] == '/': # line comment + j = text.find('\n', i) + j = n if j < 0 else j + out.append(text[i:j]); i = j; continue + if c == '/' and i + 1 < n and text[i + 1] == '*': # block comment + j = text.find('*/', i + 2) + j = n if j < 0 else j + 2 + out.append(re.sub(r'[^\n]', ' ', text[i:j])); i = j; continue + if c in '"\'': # string / char literal + q, j = c, i + 1 + while j < n and text[j] != q: + j += 2 if text[j] == '\\' else 1 + j = min(j + 1, n) + out.append(re.sub(r'[^\n]', ' ', text[i:j])); i = j; continue + if c == '{': + depth += 1 + elif c == '}': + depth = max(0, depth - 1) + out.append(c if (depth == 0 or c == '\n') else ' ') + i += 1 + return ''.join(out) + + def resolve_conflicts(slate): """Drop drafts whose externs contradict (a) the destination TU's OWN existing declarations, or (b) an earlier draft landing in the SAME file. @@ -303,7 +348,7 @@ def resolve_conflicts(slate): if path not in seen_by_file: t = {} try: - for d in DECL.findall(live_text(open(path).read())): + for d in DECL.findall(_depth0(live_text(open(path).read()))): s = sym_of(d) if s: t[s] = typesig(d) except OSError: @@ -317,7 +362,7 @@ def resolve_conflicts(slate): path = st.path if st else '' seen = table(path) body = open(e['draft']).read() - ds = [(sym_of(d), typesig(d)) for d in DECL.findall(body)] + ds = [(sym_of(d), typesig(d)) for d in DECL.findall(_depth0(body))] ds = [(s, t) for s, t in ds if s] # A DRAFT'S OWN DEFINITION IS A DECLARATION TOO (§20 / wave law 3, the DEF-side wall). # Only `extern` lines were being compared, so a draft defining `s32 func_X(...)` against a