mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-10-04 00:20:10 -04:00
fix(phase-29): reconcile_tu dropped the sibling declarators of a multi-symbol extern line
THE DEFECT (on the banking path — gate_stage runs reconcile_tu): its rewrite replaced the draft's declaration LINE with the TU's declaration of the ONE conflicting symbol. A statement can declare several: 'extern u16 D_80078EB2, D_8011F82A, D_8011F82C, D_80078EB4, D_8011F8C4;' where only EB4 conflicts became 'extern s16 D_80078EB4;' — four symbols silently gone. WHY IT HID: the draft does not fail at the declaration. It fails later with 'D_8011F82A undeclared' at a USE, several conflicts down a peeling chain, nowhere near the cause. I peeled four separate 'next conflicts' out of func_80176218 before dumping ALL cc1 errors in ONE build and seeing three undeclared symbols that the tool itself had removed. FIX: group the plan by STATEMENT rather than by symbol; re-emit EVERY declarator (TU's version for the conflicting ones, the draft's own for the rest); note multi-declarator statements; and when a statement cannot be re-parsed, say so loudly instead of emitting only the planned symbols. VERIFIED: all 5 declarators survive, and the same draft now reconciles 3 symbols instead of 2 — the dropped ones had been hiding a further conflict. cookbook §95. The law (R32 again): a transform that REPLACES a syntactic unit must account for everything that unit contained — the STATEMENT, not the line, is the unit of a C declaration. Diagnostic: when a draft fails in a chain, stop peeling one error per gate cycle; splice once and dump every cc1 error, because the shape of the whole set names the cause.
This commit is contained in:
@@ -6958,3 +6958,38 @@ copies. Byte-gate the lift ALONE first (it must be neutral — it was, `d19c9580
|
||||
> **Cost of not doing this:** this family sat recorded as a bimodal "doesn't template" case across
|
||||
> two sessions. The whole diagnosis, once pointed at one sibling's real stderr, took under an hour
|
||||
> and was worth 137 members.
|
||||
|
||||
## §95 — `reconcile_tu` dropped the SIBLING declarators of a multi-symbol `extern` line (Phase 29 SESSION-21, `func_80176218`)
|
||||
|
||||
`reconcile_tu` conforms a draft's DATA declarations to what the target TU can see — the right
|
||||
question (§ the retired `reconcile_decls` asked the fleet, which has no single answer). Its rewrite
|
||||
replaced the draft's **declaration LINE** with the TU's declaration of the conflicting symbol. But a
|
||||
declaration statement can declare SEVERAL symbols:
|
||||
|
||||
```c
|
||||
extern u16 D_80078EB2, D_8011F82A, D_8011F82C, D_80078EB4, D_8011F8C4; /* only EB4 conflicts */
|
||||
```
|
||||
became
|
||||
```c
|
||||
extern s16 D_80078EB4; /* four symbols GONE */
|
||||
```
|
||||
|
||||
**Why it was hard to see:** the draft does not fail at the declaration. It fails later with
|
||||
`D_8011F82A undeclared (first use this function)` — pointing at a USE, several conflicts down a
|
||||
peeling chain, nowhere near the cause. I peeled four separate "next conflicts" out of this one draft
|
||||
before dumping ALL cc1 errors in a single build and seeing three undeclared symbols that the tool
|
||||
itself had removed.
|
||||
|
||||
**Fix:** group the plan by STATEMENT rather than by symbol, then re-emit **every** declarator — the
|
||||
TU's version for the ones that conflict, the draft's own for the rest — and note when a
|
||||
multi-declarator statement is touched. When the statement cannot be re-parsed, say so loudly instead
|
||||
of emitting only the planned symbols. After the fix the same draft reconciles **3** symbols rather
|
||||
than 2: the dropped declarators had been hiding a further conflict.
|
||||
|
||||
> **The law (R32, again):** a transform that REPLACES a syntactic unit must account for everything
|
||||
> that unit contained. Line-granular rewriting of C declarations is wrong by construction — the
|
||||
> statement, not the line, is the unit, and a statement can hold N declarators.
|
||||
>
|
||||
> **Diagnostic worth reusing:** when a draft fails in a chain, stop peeling one error per gate cycle.
|
||||
> Splice it once and dump EVERY cc1 error — the shape of the whole set names the cause
|
||||
> (three `undeclared` symbols that share one original declaration line is not three problems).
|
||||
|
||||
+37
-5
@@ -137,14 +137,46 @@ def fix(body, tu_path, fn):
|
||||
if not plan:
|
||||
return body, notes
|
||||
|
||||
# Group the plan by STATEMENT, not by symbol. A declaration statement can declare SEVERAL
|
||||
# symbols — `extern u16 D_80078EB2, D_8011F82A, D_8011F82C, D_80078EB4, D_8011F8C4;` — and only
|
||||
# one of them may conflict. Replacing the LINE with that one symbol's declaration silently
|
||||
# DROPS the other four, and the draft then fails with `D_8011F82A undeclared` several conflicts
|
||||
# later, pointing nowhere near the cause. Measured on func_80176218 (Phase 29 SESSION-21):
|
||||
# 5 declarators in, 1 out, 4 lost. Re-emit every declarator — the TU's version for the ones that
|
||||
# conflict, the draft's own for the rest.
|
||||
by_stmt = {}
|
||||
for _name, (_tu, _d, st) in plan.items():
|
||||
by_stmt.setdefault(st.text, set()).add(_name)
|
||||
|
||||
out, done = [], set()
|
||||
dropped_check = [0, 0] # (declarators seen, declarators emitted)
|
||||
for line in body.split('\n'):
|
||||
hit = next((s for s, (_t, _d, st) in plan.items()
|
||||
if st.text.split('\n')[0].strip() == line.strip() and s not in done), None)
|
||||
if hit: # the draft's decl line -> the TU's declaration
|
||||
done.add(hit)
|
||||
hit_stmt = next((s for s in by_stmt
|
||||
if s.split('\n')[0].strip() == line.strip() and s not in done), None)
|
||||
if hit_stmt: # the draft's decl line -> conform, preserving siblings
|
||||
done.add(hit_stmt)
|
||||
indent = re.match(r'^[ \t]*', line).group(0)
|
||||
out.append(indent + plan[hit][0].declaration())
|
||||
planned = by_stmt[hit_stmt]
|
||||
try:
|
||||
all_ds = [d for d in cdecl.parse(hit_stmt)
|
||||
if not (d.is_definition or d.kind == 'func' or d.storage == 'typedef')]
|
||||
except cdecl.CDeclError:
|
||||
all_ds = []
|
||||
if all_ds:
|
||||
for d in all_ds:
|
||||
dropped_check[0] += 1
|
||||
out.append(indent + (plan[d.name][0].declaration() if d.name in planned
|
||||
else d.declaration()))
|
||||
dropped_check[1] += 1
|
||||
if len(all_ds) > 1:
|
||||
notes.append(f'multi-declarator statement preserved: '
|
||||
f'{len(all_ds)} symbol(s), {len(planned)} conformed')
|
||||
else: # unparseable — emit what we planned, and SAY so
|
||||
notes.append(f'!! could not re-parse a planned decl statement; emitted only the '
|
||||
f'{len(planned)} conformed symbol(s) — siblings may be lost: '
|
||||
f'{line.strip()[:70]}')
|
||||
for nm in sorted(planned):
|
||||
out.append(indent + plan[nm][0].declaration())
|
||||
continue
|
||||
if re.match(r'\s*(extern|typedef)\b', line):
|
||||
out.append(line) # never cast inside a declaration line
|
||||
|
||||
Reference in New Issue
Block a user