Skip to content

CC fixes - #648

Merged
jgarzik merged 21 commits into
mainfrom
updates
Aug 19, 2026
Merged

CC fixes#648
jgarzik merged 21 commits into
mainfrom
updates

Conversation

@jgarzik

@jgarzik jgarzik commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

No description provided.

jgarzik and others added 8 commits August 18, 2026 19:24
`extern int a[]; sizeof a` compiled and answered 0; gcc rejects it
(6.5.3.4p1). #C90 closed the type-name form and left this one, because
neither the type nor the completeness helpers can settle it: int[],
int[n] and int[m] all intern to one TypeId and the extent lives on the
declarator. Symbol::array_is_variably_modified records whether one was
given -- the same shape as the other declaration facts the symbol
carries -- so a VLA's sizeof keeps working and an incomplete array's is
refused.

Two further defects had to go with it. The file-scope declarator loop
built an absent extent as size.unwrap_or(0), where parse_declarator
keeps None, so `int a[];` was indistinguishable from the GNU
zero-length `int a[0];` and the two paths disagreed about what
incomplete looks like. And a redeclaration reused the existing symbol
with the existing type, so `extern int a[]; int a[4];` left the object
incomplete for good; 6.2.7p4 makes the composite carry whichever extent
is known.

Also corrects TODO.md, which still said #C38 had a reproducer but no
fix -- it was fixed, and re-probing at -O0 and -O2 under qemu agrees.

Closes #C112.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`_Atomic struct S s; s.a` compiled in silence. C11 6.5.2.3p5 makes it
undefined behaviour -- the access touches part of an object whose
atomicity covers all of it, so the lock the type promises is not taken.
gcc warns; the severity is a warning rather than a rejection because the
standard makes it undefined rather than a constraint violation.

This was the last entry on TODO.md's "C11 Atomics -- remaining semantic
validation" list, and the omission meant the one operation _Atomic
exists to prevent was the one c17 never mentioned.

Both access forms warn, `p->m` judging the pointee's atomicity since
that is the object it names, and the message distinguishes a structure
from a union as gcc's does. The mirror image must stay silent and the
accept-side test pins it: it is the object's atomicity that counts, not
the member's, so `struct { _Atomic int a; } s; s.a` is an ordinary
access to an atomic member.

Recorded at #C113.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
One missing mechanism behind three symptoms. `int v; int w = v;` was
accepted and silently yielded zero. `const int c = 5; int w = c;` was
accepted and right, but only because nothing folded it. And `int w = c +
1;` one step along was rejected -- valid code gcc compiles. Fixing any
one alone leaves the others wrong, which is why the narrow attempt
during #C101 was reverted.

gcc's rule, measured: a const-qualified object with a visible constant
initializer folds in a static initializer, in arbitrary arithmetic and
at every arithmetic type, silently and even under -pedantic; without an
initializer, or through extern, it does not; a non-const object never
does.

The folding is scoped by a ConstScope parameter threaded through the
evaluator rather than a flag on the linearizer, because it is a property
of the question being asked and not of compiler state -- a flag would
let the answer depend on evaluation order. C makes a const object no
kind of constant expression, so the lax scope must not reach an array
size, a case label, _Static_assert, an enumerator or a bit-field width;
all five stay strict and are pinned. eval_static_address moves to the
lax scope too, being reachable only from the initializer path, which is
what makes `int *p = &a[c-3];` compile.

Closes #C102. Filed #C114: _Static_assert refuses `1.5 > 1.0`, whose
result is int and so is an integer constant expression -- c17 judges it
on the operand type instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Filed as noise -- 567 warnings in a CPython build -- and the noise was
the smaller half. glibc declares register_t with
__attribute__((__mode__(__word__))), so c17 sized it 4 bytes against
gcc's 8: a wrong type, silently, in a header any program may include.

A machine mode names a width, and the attribute replaces the declared
type with the one of that width in the same family, keeping the declared
signedness -- so `typedef unsigned u8 __attribute__((mode(QI)));` is
unsigned and the `int` spelling signed. Integer modes QI/HI/SI/DI/TI and
the synonyms word/pointer; floating HF/SF/DF/XF/TF; and the complex
forms HC/SC/DC/XC/TC, which were 285 of the warnings on their own --
<bits/floatn.h> declares __cfloat128 with mode(TC).

XF and TF are both sixteen bytes and not interchangeable, one being the
x87 extended format and the other IEEE binary128, so they map to their
own types rather than to a width; the test divides by three and checks
the precision rather than trusting sizeof.

A vector mode still warns: ignoring one would change what the program
computes, and vector types do not exist here.

A full CPython -O2 build now emits zero mode warnings, and 552
diagnostics in total against 1,105. Closes #C85.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`__builtin_add_overflow(-1, 5u, &u128)` reported overflow where the
answer is 4. The builtin asks whether the mathematical result is
representable, and the ordinary lowering answers by computing in a type
twice the destination's width and asking whether narrowing lost
anything -- which has nothing to compare against when the destination is
itself 128 bits, so a fallback examined the result directly on operands
already converted to the destination. That conversion is not
value-preserving for a negative operand with an unsigned destination.

For two genuinely 128-bit operands the answer still needs 129 bits, as
#C62 recorded. But when both operands are narrower the exact computation
fits, and only the check changes: from "did narrowing lose anything" to
"is the exact value representable", which for a signed exact value in an
unsigned destination is just "is it negative".

The bound is counted in magnitude bits rather than assumed, and that is
the part worth keeping: `(u64)-1 * (u64)-1` is just under 2^128, which
unsigned __int128 holds and __int128 does not, so a shortcut gated on
"narrower than the destination" alone would let the exact computation
wrap and then check a value it never saw. A pre-existing test caught
exactly that.

Verified against gcc on 24 rows and on aarch64 under qemu. Closes #C62.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Filed from one symptom -- `_Static_assert(1.5 > 1.0, "x")` refused --
and the symptom understated it twice over.

The expression was not being refused as non-constant; it was folded to
false, because the parser's constant evaluator truncates a floating
literal to i128 before folding, so `1.5 > 1.0` became `1 > 1`. That
folder evaluates array sizes, enumerators, case labels and
_Static_assert, so `enum E { X = 1.5 > 1.0 }` was 0 and
`int a[1.5 > 1.0 ? 4 : 8]` took the wrong branch and allocated eight
elements. The same arm compared signed whatever the operand types, so
`(unsigned)-1 > 0` was false too. And a cast from floating arithmetic
evaluated its operand through the integer folder, making
`(int)(1.5 + 1.5)` two rather than three.

A comparison of floating operands has an integer result and so is an
integer constant expression however its operands are spelled;
unsignedness comes from the operands under the usual arithmetic
conversions. Both folders were fixed, since each evaluates a different
set of contexts.

The duplication is the standing hazard and is filed as #C115: two
implementations of 6.6 that have drifted, only one of which had the
unsigned-comparison and width-masked-shift treatment earlier work gave
it. Closes #C114.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
linearize_atomic.rs says the lock-free-size aggregate gap is 'Recorded in
doc/TODO.md'. Nothing was ever recorded there, so a deferral with a good
reason was invisible to anyone reading the documents rather than the
source -- the same failure as the stale #C38 note this series already
corrected.

The gap itself: `_Atomic struct S { int a; }` read as a whole object
warns and falls through to a non-atomic struct copy, where gcc emits a
plain 4-byte load. Scalars of 1, 2, 4 and 8 bytes are lock-free and an
aggregate of the same size gets nothing; anything larger is refused,
which is deliberate per #X1. Doing it properly needs the
value-versus-address convention for small aggregates settled first.

Filed as #C116, with the TODO.md entry the comment promised.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous commit added the TODO.md entry and corrected the source
comment; this is the audit entry that goes with them. Split only because
the first attempt's edit did not apply.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jgarzik
jgarzik requested a lite review from Copilot August 18, 2026 21:59
@jgarzik jgarzik self-assigned this Aug 18, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the cc crate to fix several C17/GCC-compatibility issues around machine-mode attributes, sizeof on incomplete arrays, constant-expression folding (including static-initializer “const object” folding), atomic aggregate member diagnostics, and 128-bit checked-arithmetic overflow semantics. It also adds/extends diagnostics and codegen tests plus corresponding audit/TODO documentation updates.

Changes:

  • Implement __attribute__((mode(M))) for scalar/complex modes (keeping vector modes as warnings) and add tests ensuring implemented modes are silent and type selection matches expectations.
  • Reject sizeof on incomplete array expressions while preserving VLA and completion-by-redeclaration behavior; add diagnostics + codegen coverage.
  • Scope constant folding so static initializers can fold visible const objects (gcc behavior) without weakening strict constant-expression contexts; fix float/unsigned comparison folding and add coverage.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
cc/tests/diagnostics/mod.rs Adds diagnostics coverage for implemented mode, incomplete-array sizeof expression rejection, atomic-member warnings, and static-initializer folding boundaries.
cc/tests/codegen/misc.rs Adds runtime/codegen validation for array sizeof behavior and const folding in static initializers.
cc/tests/c99/types.rs Adds codegen test asserting mode(M) selects correct types/signedness/precision.
cc/tests/c99/expressions.rs Adds test pinning correct constant folding for float and unsigned comparisons.
cc/tests/builtins/intrinsics.rs Adds coverage for 128-bit checked arithmetic builtins with mixed signedness.
cc/symbol.rs Records whether an array declarator was variably modified to distinguish VLA vs incomplete for sizeof expression operands.
cc/parse/parser.rs Implements mode mapping, fixes array extent handling and redeclaration completion, and improves parser-side constant folding behavior.
cc/parse/expression.rs Enforces sizeof-expression completeness for incomplete arrays, and adds atomic aggregate member-access warning emission.
cc/ir/linearize.rs Adjusts 128-bit checked-arithmetic lowering to correctly detect representability in mixed-signedness cases.
cc/ir/linearize_stmt.rs Introduces ConstScope and folds visible const objects in static initializers; improves float-compare folding in const eval.
cc/ir/linearize_init.rs Switches initializer folding to initializer-scoped const evaluation and diagnoses non-constant object reads.
cc/ir/linearize_atomic.rs Updates comment to match new audit/TODO references for the recorded atomic-aggregate deferral.
cc/doc/TODO.md Updates TODOs to reflect closed findings and better document remaining atomic-aggregate limitation.
cc/audit.md Marks multiple findings fixed and records new behavior/tests for #C85/#C112/#C102/#C113/#C114/#C62, etc.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread cc/parse/parser.rs
Comment thread cc/ir/linearize_stmt.rs
jgarzik and others added 13 commits August 18, 2026 22:39
Two defects in the mode attribute #C85 added, found by CI and by review
within a day of it landing.

TF and TC mapped to _Float128 on every target, but arch::has_float128()
is `os != MacOS` and c17 deliberately predefines no __FLT128_* family
there -- precisely so <float.h> cannot advertise a type whose every
operation fails to link. The mode attribute handed that type back
anyway, so mode(TF) on Apple arm64 needed __divtf3 and __multf3 and
turned macOS CI red while both Linux targets passed. They now warn and
leave the declared type unchanged, as a vector mode does, gated on
TypeTable::has_float128 rather than a second spelling of the condition.

Separately, apply_pending_mode was called at the three typedef sites
only, while pending_mode is set for any declarator and -- unlike
pending_alignas, cleared at four declaration boundaries -- was never
cleared. A mode on a non-typedef was therefore dropped and then survived
onto whatever came next: `int a __attribute__((mode(QI))); typedef int
T;` left a four bytes and made T one. The mode now applies wherever a
declarator's type becomes final, before alignment attaches to it, and is
cleared at all four boundaries.

Fixing that produced a clean instance of #C54: the `mut` binding one
site needed is in parse_declaration, which is #[cfg(test)]-gated, so
cargo build succeeded and only cargo test failed.

Recorded at #C117.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`enum E { A = 1 << 64 };` exited 101 on an unreachable! in
enum_underlying_type, and so did `3 << 63` and `1 << 100`. Its premise --
"a non-negative maximum always fits u64 or overflows i128" -- is false:
nothing clamps an enumerator to 64 bits and the folder computes in i128.

It is now the same diagnostic the signed branch already had. A compiler
diagnoses rather than aborting, whatever else is wrong.

One divergence remains and is deliberate: gcc accepts these, folding the
shift to 0 because it truncates to the expression's type, and c17
rejects them because neither folder truncates. That is #C115, and this
path stops being reached once it lands.

The test asserts the message rather than mere failure -- a panic also
exits non-zero, so an exit-status-only test would have passed against
the crash. Recorded at #C118.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two defects that meet on the wide bit-field.

emit_bitfield_load_bytewise walked every byte of the access span with no
"does this byte hold any of the field's bits?" test -- the skip its
store sibling has always had. A field in a window wider than itself read
the object's padding, and every __int128 bit-field is such a field: its
window is sixteen bytes. The stray bytes were also shifted past the
64-bit carrier's width, which x86-64 masks (folding padding into the
result) and aarch64's assembler rejects, so the same source would not
build there. The comment claiming every shift was in range held only for
the packed case the helper was written for.

The brace initializer then converted twice: to the member's type,
correctly, and again to a type derived from the span, where
bitfield_storage_type answers `unsigned int` for anything but 1, 2, 4 or
8 bytes. A 64-bit field kept 32 bits of its initializer. The second
conversion is gone; emit_bitfield_store takes the member-typed value as
the assignment path already hands it over, which is why `p.a = ~0ULL`
was right and `= {~0ULL}` was not.

The test dirties the padding deliberately -- with it zero the load
defect is invisible, which is how #C98's width cap came to promise that
widths up to 64 "keep working".

Recorded at #C119 and #C120.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`__builtin_fabsl(-3.5L)` returned 2.5e-4932. Both it and
__builtin_signbitl lowered to the *double* opcode -- the code said so,
"treat as 64-bit for now" -- whose emitter moves the argument as a double
and calls fabs/__signbit. On x86-64 a long double is the 80-bit x87
format, so that read its mantissa.

They are ordinary calls to fabsl and __signbitl now, built by the same
synthesize-a-declaration route the __builtin___*_chk family uses. That
gets the long-double ABI from the call path, which already carries one
for __mulxc3, so no backend work was needed; the Fabsl and Signbitl AST
variants and their lowering arms are gone with it.

The signbit family is also normalised to 0/1. 7.12.3.6 permits any
nonzero value and the library entry points return the sign bit in place
-- 8, 128 and 512 for the three widths, which did not agree with one
another. gcc is no more consistent: 512 on x86-64 for a runtime long
double, 1 for a constant one, 1 for both on aarch64. All conforming, so
this is predictability rather than a fix, and it is recorded as such.

The test reads its operands from an array; the constant form was always
right, which is what hid this. Recorded at #C121.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`char big[5000000000];` compiled clean and reported sizeof 536870911.
size_bits answers in a u32, and the Array arm capped at u32::MAX with a
comment saying so; nothing bounded an extent except the negative check,
so a wrong sizeof propagated into every layout, offset and copy built
from that type.

gcc accepts these -- its own bound is far higher -- so this is an
implementation limit rather than a constraint violation, and the message
says which. TypeTable::MAX_OBJECT_BYTES names the bound and
Parser::derive_array_type enforces it at the one place the three
declarator loops now share. It measures against the element type, so the
outer dimension of char a[16385][32768] is checked against an inner
extent already known to be in range.

A member list can reach the bound with no single member near it, so the
struct and union specifier checks the laid-out size too; that arm's
`(size * 8) as u32` had been a silent truncation rather than a cap. Both
saturations remain as floors and are now unreachable from source.

Recorded at #C122, with #C123 filed for what the probe turned up beside
it: a rejected abstract array declarator reports the backtracking
fallback's error, not the real one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There were two C17 6.6 walks -- one in the parser for array bounds,
enumerators, case labels, bit-field widths and _Static_assert, one in
the linearizer for static initializers and branch folding -- and they
had drifted. The parser had a floating-comparison arm the linearizer
lacked, the linearizer had _Alignof and the const-object scope the
parser lacked, and each carried fixes the other did not, so the same
expression could be one value in a declaration and another in the
initializer beside it.

They are one walk now, in cc/constexpr.rs, over a ConstEnv trait whose
only members are what the two hosts genuinely answer differently: what
an identifier means, which struct a member path starts from, and how a
floating subexpression folds. The parser's copy of eval_pointer_constant
and its now-dead float helpers went with it.

The drift had a wrong answer in it. The parser's FloatLit arm truncated
a floating literal to an integer, which 6.6p6 admits only as the
immediate operand of a cast, so `int a[1.5];`, `enum E { X = 1.5 };`,
`struct S { int b:1.5; };` and `_Static_assert(1.5, "")` all compiled
where gcc rejects each -- and at block scope the array did not merely
compile, it became a variable length array sized from a double. The arm
is gone, check_array_size_type gives gcc's "size of array has
non-integer type" instead of a misleading VLA message, and
__builtin_constant_p asks the floating fold as well, since a floating
constant is still a constant.

The review's second point closes with it: the subscript in a pointer
constant stays in the scope that reached it, so
`const int c = 5; int *p = &a[c - 3];` folds.

Recorded at #C124, with #C125 filed for the -Wshift-count-overflow
warning c17 still does not emit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…l by size

C evaluates every operation *in* a type, and the result of one is an
operand of the next. The folder carried a full-width i128 instead, so
`-1u / 3u` was 0 rather than 1431655765 -- the negative was still
negative when the division saw it -- `(unsigned)-1 % 7u` was 4294967295
rather than 3, `-1u >> 1` was an arithmetic shift, and a cast did not
convert at all: `(unsigned char)-1` stayed -1 and `(short)70000` stayed
70000.

constexpr::normalize reduces every node's value to what its own type can
hold. The cast falls out of that for free, since a cast node's type is
the type cast to, and _Bool gets its 0-or-1 conversion rather than the
low byte. Division, remainder and right shift additionally ask the
operand's signedness, which matters only at 128 bits: there is no
narrower type to reduce to, and (unsigned __int128)-1 really is a
negative i128. Thirty-five folded expressions were diffed against gcc.

That exposed a second bug, latent until now. 6.4.4.1p5 picks the first
of unsigned int, unsigned long, unsigned long long that can represent
the value, but the suffix arm returned unsigned int unconditionally, so
`sizeof 0xaaaaaaaaaaaaaaabu` was 4. Harmless while constants were
carried at full width; fatal once they narrow. The CPython gate caught
it: math.comb(5, 2) returned 85899345930, from a static const uint64_t
table whose 0xaaaaaaaaaaaaaaabu entry was emitted as .quad 2863311531.

Recorded at #C126 and #C127, with #C128 filed for what the differential
harness turned up beside them: `#x` stringification drops every
multi-character punctuator.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`#define S(x) #x` then `S(a >> b)` yielded "a  b", and so did <<, >>=,
>=, &&, ||, ++ and every other operator of more than one character.
SpecialToken numbers those from 256 up, and both stringify_arg and
tokens_to_text printed only codes below that, letting the rest fall
through a catch-all. 6.10.3.2p2 asks for the spelling of the
preprocessing token, so an assertion or logging macro was printing
something that was not the expression it had been given.

SpecialToken::spelling supplies the missing text as an exhaustive match,
so a punctuator cannot be added without one, and from_code turns the
stored u32 back into the variant. -E was never affected; it rebuilds its
text by another route.

Found while diffing folded constants against gcc, where a #e label in
the harness came out as `-1u1`. Twenty-seven stringifications now agree
with gcc. Recorded at #C128.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
show_include_chain and its " (through ...)" note were live code, but the
only three functions that ever set Stream::include_pos -- Stream::included,
StreamTable::add_included and init_included_stream -- were #[cfg(test)],
and every production registration went through init_stream, which leaves
it None. Gating a constructor had made un-gated code unreachable: a full
CPython build produced zero "through" lines across 552 diagnostics, so a
warning inside a system header never said which #include brought it in.

The three are un-gated, and include_file and include_builtin call
init_included_stream with the #include directive's own position, which
both already had to hand. The same CPython build now produces 547.

Position::bad was the other gated constructor and goes the other way:
nothing in the compiler ever made a bad position, so do_diag's is_bad()
early return was dead. Both are deleted rather than un-gated, and the
unit test that existed only to call them is replaced by one that checks
the chain is recorded in order.

Part of #C54, recorded at #C129.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#C54 filed twelve #[cfg(test)] items as dead. Counting callers says
otherwise for eight: TypeTable::params and is_variadic have 17 and 15 in
parse::test_parser, Function::dominates has 10 in ir::dominate,
diag::{stream_name, stream_prev, error_count, warning_count} back this
module's registry and counter tests, and TypeId::is_valid and
Linearizer::new_no_ssa have one each. Those are test support and
#[cfg(test)] is the right way to say so.

What is dead is opt.rs's four helpers -- get_pseudo, get_const_val,
get_const_fval, is_const -- under a header reading "used by tests and
future passes". No pass used them, the only callers were four tests of
the helpers themselves, and every real consumer has its own get_pseudo.
Helpers, gated imports and self-referential tests are gone.

Two headers were wrong about the code beneath them. types.rs's
"Test-only methods (used by tests but not production code)" sat above
format_type with 27 production callers, unsized_array_levels with 13,
is_composite_complete with 5 and array_size with 4; diag.rs's "for
tests" said nothing about why. Both now describe what is there.

Part of #C54, recorded at #C130.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Parser::variable_array_levels was line-for-line
TypeTable::unsized_array_levels, and cflow, ctags and cxref each carried
a byte-identical exit_code() -- the one that has to consult both error
counters, since the front end keeps its own and a tool reading only one
exits 0 after reporting errors. The first is gone; the second lives in
cc/tools.rs, a module the c17 binary does not compile.

The third duplicate was not equivalent. cflow::format_type was a partial
copy that dropped everything below the top level, printing int[] whatever
the extent, int() whatever the parameters, and bare `struct` for a tagged
one. It calls TypeTable::format_type now.

Swapping it over surfaced two defects in the shared formatter, fixed
here. A multi-dimensional array printed its extents backwards, so
`int m[4][8]` came out `int[8][4]` in cflow output and in every
diagnostic that named the type. And a pointer was written `char*` where
gcc and the source write `char *`, with `char **` joined.

Recorded at #C131, with #C132 filed for what remains: a pointer to an
array is still spelled `int[8] *` rather than gcc's `int (*)[8]`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
parse_declaration and parse_function_def were #[cfg(test)] copies of the
declaration and function-definition halves of parse_external_decl, and
they had drifted: neither cleared pending_alignas, pending_mode or
pending_fn_attrs, which the real entry point does first. So 52 unit
tests in parse::test_parser were checking a parser no translation unit
ever ran through -- the same failure mode as the include-chain trace, a
#[cfg(test)] gate on something production also needs.

Both copies are deleted. parse_decl and parse_func call
parse_external_decl and destructure the ExternalDecl, failing loudly on
the wrong arm. All 1051 unit tests pass unchanged against the production
path, so nothing in the drift had been load-bearing -- but nothing had
been checking either.

Recorded at #C133, which closes #C54: the include chain (#C129), the
genuinely dead helpers (#C130), the duplicated helpers (#C131) and this
were its four parts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The close was meant to ride along with the #C133 commit and did not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jgarzik
jgarzik merged commit 114cffe into main Aug 19, 2026
16 checks passed
@jgarzik
jgarzik deleted the updates branch August 19, 2026 12:01
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.

2 participants