Skip to content

docs: brace position on an inline structor is load-bearing for header-offsets - #2068

Merged
andrewboudreau merged 1 commit into
mainfrom
docs/inline-structor-brace
Aug 31, 2026
Merged

docs: brace position on an inline structor is load-bearing for header-offsets#2068
andrewboudreau merged 1 commit into
mainfrom
docs/inline-structor-brace

Conversation

@andrewboudreau

Copy link
Copy Markdown
Collaborator

Follow-up to #2065, same file. Two things the @symbol section left out, both of which the inline-destructor wave will hit immediately.

1. Brace position on an inline structor is load-bearing

Inlining a destructor to make it scoreable moves a function body into a struct that tools/check_header_offsets.py parses. That gate arms its body-skip only when the brace is on the signature line:

if n == 0:
    depth = line.count("{") - line.count("}")
    if depth > 0:
        skip_body = depth
    continue

Written Allman, skip_body never arms and the body's lines fall through to declaration parsing.

I measured it rather than reasoning about it, rewriting nothing but the destructor of include/dScMgBase_c.h — a derived class that declares its destructor first, which is the shape every inline-destructor promotion produces:

one line, with body    40 commented fields, 0 mismatched, 0 unparsed, spans 0x4660
one line, empty {}     40 commented fields, 0 mismatched, 0 unparsed, spans 0x4660
Allman, with body       0 commented fields, 0 mismatched, 2 unparsed, spans 0x50
Allman, empty           0 commented fields, 0 mismatched, 1 unparsed, spans 0x50

The UNPARSED line exits 1, so it is loud — but green on origin/main, which makes it a merge-tree-only red: premerge_check.py shows it, the branch's own CI does not. The quiet half is worse. One unparsed line suppresses the field walk for the entire header, so 0 commented fields is not a clean pass, it is no check at all: forty fields stopped being checked and the span fell back to the base's 0x50. That is this tool's own documented silent-no-op shape, arriving a fourth way.

Two conditions narrow it, and both hold for exactly the promotions this document covers — a derived class (a non-derived one is called unmodelled at its first method line and never reaches the brace) whose structor is declared before any field, the dScene_c.h key-function convention. include/ArrowLift.h, whose destructor follows its fields, measures identically in both styles; the method line ends the field list and the stray brace is never read. And an empty Allman body breaks it where an empty one-line body does not — it is the newline that costs, not the statements.

2. A marker on a still-unnamed member buys nothing for that member

score_member recomputes real_name from the symbol rather than the fragment, so a member still called func_ov006_0210a534 fails that criterion whichever text it is scored against. Marking it is still right, because the boundary protects its neighbour — but do not expect the marker to move its own score. Renaming does that.

Scope and limits

Docs only: one file, notes/tu-promotion-conventions.md. No header, source or config file is touched, so no ROM byte can move.

This is a defect in the gate, not in the style, and I have filed it as one — arming skip_body from a following-line brace is a small change but it risks newly-reding headers and wants measurement across all 508 before it lands. Until then brace position is load-bearing and belongs in the conventions doc.

The Allman hazard was first noticed by another session on a different header; I did not take it on trust — everything above is my own reproduction, on a header that is on main, and it corrects the original report in two ways (the empty-body case also breaks, and the declared-before-fields precondition is required).

…-offsets

Inlining a destructor to make it scoreable moves a function body into a
struct that check_header_offsets.py parses. That gate arms its body-skip
only from a brace on the signature line, so an Allman brace falls through
to declaration parsing and is reported UNPARSED -- and one unparsed line
suppresses the field walk for the whole header.

Measured by rewriting only the destructor of include/dScMgBase_c.h:

  one line, with body    40 commented fields, 0 unparsed, spans 0x4660
  one line, empty {}     40 commented fields, 0 unparsed, spans 0x4660
  Allman, with body       0 commented fields, 2 unparsed, spans 0x50
  Allman, empty           0 commented fields, 1 unparsed, spans 0x50

Two conditions narrow it and both hold for inline-destructor promotions:
a derived class whose structor is declared before any field. ArrowLift.h,
whose destructor follows its fields, measures identically in both styles.
An empty Allman body breaks it where an empty one-line body does not.

Also records that a marker on a still-unnamed member cannot lift that
member's own score, since score_member recomputes real_name from the
symbol -- the marker is there to protect its neighbour.

Docs only. No header, source or config file is touched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WdCK1xgrJdiJzPCh3bAJfQ
@tangos-validator

tangos-validator Bot commented Aug 31, 2026

Copy link
Copy Markdown

✅ PR validation — Passed

noverify: no source/build-data changes in this PR

Each changed src/*.c|*.cpp is compiled and its relocated bytes compared to the binary data on a private build box. Passing requires every changed file to reproduce the ROM byte-for-byte with correct relocation targets — this catches WRONG-DEST relocations and non-reproducing near-misses that ledger-scoped linkcheck skips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant