diff --git a/docs/matching-cookbook.md b/docs/matching-cookbook.md index b66c255a5..bb2e110e3 100644 --- a/docs/matching-cookbook.md +++ b/docs/matching-cookbook.md @@ -17976,3 +17976,9 @@ more. `pregate_check`'s CONFLICTING-EXTERN scan should have caught the `D_800727 of them and did not: its declaration regex is single-line, and the offending declaration was a multi-line `extern struct { ... } *D_x;`. **A pre-gate check that sees 90% of declarations converts a one-rebuild-per-conflict loop into a single pass โ€” that is where the leverage is, not in the drafting.** +**Shipped the same session**: `pregate_check` now scans brace-bodied externs (`extern struct { ... } +*D_x;`) with the normalized body as part of the signature, so two identical struct declarations stay +silent while a struct-vs-`void *` clash FAILs. Negative-controlled four ways โ€” and one of those +controls caught a trap worth its own line: **a synthetic test using a fake symbol name (`D_x`) reported +CLEAN for a conflict the tool does detect**, because `sym_of` only recognizes real project symbol +spellings. A negative control must use names the system would accept, or it tests nothing. diff --git a/tools/pregate_check.py b/tools/pregate_check.py index a8b90aaa6..a1dba1e34 100644 --- a/tools/pregate_check.py +++ b/tools/pregate_check.py @@ -62,6 +62,11 @@ def _depth_map(masked): return out +STRUCT_EXTERN = re.compile( + r'^[ \t]*extern\s+(?:const\s+|volatile\s+)*(struct|union)\s*\{([^{}]*)\}\s*(\**)\s*(\w+)\s*(?:\[[^\]]*\])?\s*;', + re.M | re.S) + + def _norm_sig(sig): """`void f()` and `void f(void)` are not a conflict worth blocking a rebuild over: C89 calls the first an unspecified parameter list, and gcc-2.7.2 accepts the pair. Normalize both to @@ -155,6 +160,18 @@ def check_text(path, text): # 4. one symbol declared two incompatible ways anywhere in the final text decls = {} + # BRACE-BODIED EXTERNS FIRST (P31 S53). gm.DECL is single-line, so a draft declaring + # extern struct { u8 pad[0x34]; s32 (*field_0x34)(s32); } *D_80072780; + # was invisible to this check while a sibling declared the same symbol `void *` โ€” the clash + # surfaced only as a compile error, one rebuild later (ยง183.5). Bodies are flat here (no nested + # braces), and the normalized body is part of the signature so two IDENTICAL struct declarations + # do not read as a conflict. + for m in STRUCT_EXTERN.finditer(masked): + if depth[m.start()]: + continue + body = ' '.join(m.group(2).split()) + sig = (f'{m.group(1)}{{{body}}}{m.group(3)}', '') + decls.setdefault(m.group(4), (m.start(), sig)) for m in gm.DECL.finditer(text): if depth[m.start()]: # block-scope decl: private to its function continue