[codex] RUE-366: validate parameterized test keys#4
Closed
steveklabnik wants to merge 276 commits into
Closed
Conversation
…module (RUE-121 phase 2) Phase 2 of the backend-dedup effort: the StructInit and ArrayInit lowering arms — the eager flattening that populates the slot cache — were structurally identical hand-mirrored copies in the two cfg_lower.rs files (the x86 copy matched on TypeKind, the aarch64 copy on is_struct()/is_array(); same semantics, drifted spelling). They now live once in agg_slots.rs as lower_struct_init/lower_array_init. SlotBackend grows four thin leaf methods: map_value (value_map insert), emit_reg_move, emit_load_zero (MovRR/MovRI32 on x86, MovRR/MovImm on aarch64), and collect_array_scalars (the backends' existing wrapper over the already-shared types::collect_array_scalar_vregs). Verified the refactor is behavior-preserving the same way as phase 1: --emit asm output is byte-identical before/after on a 37-program corpus (aggregate-heavy shapes: nested structs, fieldless structs, String fields, multidimensional arrays, arrays of structs, struct-with-array fields, aggregate args/returns) on BOTH x86-64 and aarch64. Full test.sh green. Remaining phases tracked in RUE-121: consumer-side store loops (phase 3), scheduler/liveness sharing (phase 4). Part of RUE-121. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
The chumsky error-conversion path built spans with a defaulting to_rue_span() that dropped the FileId, so every parse error span carried FileId::DEFAULT. In a multi-file compile the diagnostic renderer's fallback then picked an arbitrary source file: an E0100 in b.rue could be rendered with a caret on perfectly valid code in a.rue, with wrong line/col and a mismatched snippet. (The E0102 path already threaded the id via parser state, which is why only some errors misattributed.) The defaulting helper is deleted outright so the footgun can't return. convert_error() now takes the FileId (threaded from ChumskyParser::parse) for both the primary span and the labelled-context spans, and the error_recovery() Item::Error span tags the file id via map_with + parser state. Three CLI cases in crates/rue-cli-tests/cases/multifile_errors.toml pin the attribution: parse error in the second file, in the first file (swapped order), and an E0102 control — each asserting the correct bad.rue:line:col appears and the valid file's name never does. Full test.sh green. Fixes RUE-115 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…schedulers Remaining items of the aarch64-emitter correctness epic (items 5 and the rest of 4/6). Branch fixups (item 5): apply_fixups masked branch offsets to the encoding field width with no range check — a B beyond +-2^25 instructions or a B.cond/CBZ beyond +-2^18 would silently branch somewhere else entirely. Both fixup kinds now ICE on out-of-range offsets, mirroring the x86 emitter's rel32 check, plus a debug_assert that branch sites/targets are 4-byte aligned. Scheduler clobbers (item 4, second half): build_dep_graph created edges INTO a clobbering instruction (clobberer after prior readers/writers) but never recorded the clobbered registers as written by it. A later instruction writing or reading a clobbered register therefore had no edge back to the clobberer and could be hoisted above it — e.g. on x86 a `mov rdx, imm` scheduled above the CQO/IDIV that clobbers rdx. Clobbers now update last_writer/last_readers exactly like writes, on both backends. On aarch64 every current clobberer (Bl, Svc) is also a scheduling barrier, so this is defense-in-depth there; on x86 CQO/CDQ/IDIV clobber without being barriers. Verified behavior-preserving on the 37-program asm corpus (byte-identical on both targets at the same base) — the new edges only forbid reorderings that would have been wrong. Test gaps (item 6, partial): three scheduler unit tests pin the clobber ordering (x86 WAW + RAW vs CQO; aarch64 WAW + RAW vs SVC at the dep-graph level), and the two @syscall result-READING spec cases — previously x86-only, which is exactly how the Svc result-hoist bug shipped undetected — now have aarch64-linux mirrors (getpid = 172) that CI's native arm64 job executes. Remaining in the epic after this: TestEmitter drift and aarch64 emitter fuzz coverage. Part of RUE-129. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…fixed
Two repro-confirmed miscompiles from the never-type/loop interaction, both
producing memory-unsafe binaries from innocuous source:
- `let x: i32 = loop { break; }; x` compiled cleanly and segfaulted: the
loop expression was typed `!` unconditionally, so binding it passed type
checking, but the break-exit path never produced a value — the binding
read an uninitialized slot. As a function tail expression the break-exit
block had no ret and control fell off the end of the function. (RUE-76)
- `loop { break 42; }` parsed as `break` plus an unreachable expression
statement, compiled with only a warning, and segfaulted the same way —
the spec forbids a value operand on break. (RUE-56)
Sema (both implementations) and HM inference now track a per-loop break
flag (loop_break_stack): a loop containing a break that targets it is typed
`()` — Rust semantics — so both RUE-76 repros become honest E0206 type
errors; a loop with no reachable break keeps type `!` (still usable in
never position). The parser now parses an optional value operand on break
(carried through RIR) and sema rejects it with new diagnostic E0502
"'break' with a value is not supported"; inference deliberately does not
count a valued break as a loop exit so E0502 surfaces rather than a
confusing type mismatch.
Spec: 4.8:17 rewritten (no-break loop is !), 4.8:21 rewritten (loop with
break is ()), new legality rule 4.8:22 (break must not carry a value).
Traceability stays 100%.
Tests: CLI cases in crates/rue-cli-tests/cases/loop_break.toml (both
repros as compile_fail, break-as-statement and no-break controls,
break-with-value in loop and while); spec cases for 4.8:21/4.8:22.
Full test.sh green.
Fixes RUE-76
Fixes RUE-56
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
…s fixed Sema's place-tracing path (Sema::try_trace_place) emitted PlaceRead and PlaceWrite for projections — field reads, array indexing, method receivers, call arguments, @dbg operands — without ever inserting the root variable into used_locals. Only the comparison path and bare VarRef loads marked usage, so a variable accessed solely through a projection warned as unused: a tail-position `s.a`, `let x = s.a`, `g(s.a)`, `a[0]`, `@dbg(s.a)`, and a method call on a field receiver (`h.s.len()`) all fired the lint on a clearly-used variable. (`s.a == 5` happened to be fine because comparisons take a different path — which is why the gap survived.) One marking point fixes the family: the try_trace_place wrapper now inserts the trace's root variable into used_locals on every successful trace. This also makes projection writes (`s.a = 7`) count as a use, matching Rust's lint; direct assignment to the variable itself (`x = 6`) does not go through place tracing and intentionally still warns. Eight new UI cases in crates/rue-ui-tests/cases/warnings/unused.toml: both RUE-135 repros plus call-argument / array-index / @dbg / field-write no-warning cases, and two still-warns controls (genuinely unused struct variable; direct-assignment-only variable). Full test.sh green. Fixes RUE-135 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…se 3) Phase 3 of the backend dedup: every site that writes an aggregate's slots — the Alloc arm (array/struct/String branches), the Store arm's whole-aggregate paths, and all nine store loops in lower_place_write / lower_place_write_with_projections (frame-slot stores, inout-pointer stores, and dynamically-addressed stores) — was a hand-mirrored loop pair across the two cfg_lower.rs files. They now iterate via two shared primitives in agg_slots.rs: store_slots(vals, base_slot) one frame store per slot store_slots_through_ptr(vals, ptr, static_off) descending ptr - off - i*8 SlotBackend grows the two corresponding leaf methods (emit_store_slot = MovMR/Str; emit_store_through_ptr = MovMRIndexed/StrIndexedOffset). The Alloc String branch's three unrolled stores collapse into the same store_slots call (the 3-slot shape is already pinned by the debug_assert on the accessor result). Address computation for dynamic indices stays per-backend (Lea/SubRR64 vs AddImm/SubRR — genuinely different instruction selection). Verified behavior-preserving the same way as phases 1-2: --emit asm is byte-identical before/after on the 37-program aggregate corpus on BOTH x86-64 and aarch64 at the same base. Full test.sh green. Remaining in RUE-121: call-arg/return marshalling (phase 4), then the scheduler/liveness tables. Part of RUE-121. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…opping them link_elf collected relocation records only while merging .text sections; the .rodata/.data/.bss merge loops ignored theirs entirely. Any pointer stored in constant or initialized data — panic-Location file/string pointers in the runtime's .data.rel.ro tables, compiler_builtins' FUNC tables, future vtables/const tables — linked as NULL bytes. Confirmed in the wild: the runtime archive's objects carry R_X86_64_64 relocations in .data.rel.ro.* and .data.* sections that today's linker drops on the floor. The fix threads a PatchHome (Text/Rodata/Data) through each pending relocation: - collection is extracted into one collect_section_relocations helper (it was already duplicated between the ELF and Mach-O paths' .text loops) and now runs for the ELF .rodata and .data merge loops too; - the ELF apply loop selects the patch buffer and base vaddr by home, so PC-relative relocations in rodata/data measure from their own section's address, and the existing bounds checks now check against the originating buffer rather than merged_text (epic item 8 for ELF). .bss is NOBITS and cannot carry patch sites; unchanged. Two regression tests hand-build objects whose .rodata/.data carry an Abs64 relocation at offset 0 and assert the linked segment contains the target's virtual address (verified both FAIL with the collection calls disabled — they pin the drop). Full test.sh green; every suite program links through this path and runtime spot-checks behave. Scope: ELF only. The Mach-O path still drops non-text relocations and has its own layout items (3-7) tracked in the epic. Part of RUE-131 (items 1 and 8, ELF). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…port-prefixed directive names
Two diagnostic-precision fixes from the diagnostics-quality epic, both in
the logos lexer:
Invalid escapes (item 6): the error span ran from the start of the string
literal to the bad escape, and the reported character came from
slice.find('\\') — the FIRST backslash in the token — so a literal with a
valid escape before the invalid one ("hello \n \q") underlined the whole
string and named \n instead of \q. LexError::InvalidStringEscape now
carries the backslash's offset within the token and the actual rejected
character; the diagnostic puts a two-character caret exactly on the
offending escape.
@import-prefixed names (item 5): "@Important" died in the lexer with
"unexpected character: @" (with a 7-character caret) because the fused
@import token matched and then failed its word-boundary callback, which
logos does not backtrack from. A sibling regex now wins the longest-match
for "@import" plus identifier chars and tokenize() splits it into At +
Ident, so such names behave like every other @-directive. The word-boundary
callback is gone — the regex makes it unreachable. (Pre-existing and
unchanged: unknown directives like @other are accepted silently; noted in
the epic.)
The stale test pinning "@importx must be a lex error" is replaced by tests
asserting the At + Ident split with correct spans, plus a span-precision
test for the escape fix. Two UI cases pin the user-visible diagnostics
(error_contains "2:23" for the caret position). Also verified item 7 of the
epic (missing operand after binary operator) is already fixed on trunk —
the error points at the offending token now. Full test.sh green.
Part of RUE-133 (items 5, 6; item 7 verified already fixed).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Two move-checking holes from the soundness epic, both verified by repro
before and after:
Projected by-ref arguments (item 3): passing a struct field as a borrow or
inout argument (peek(borrow o.f)) sailed through sema and panicked codegen
("by-ref argument must be a variable", cfg_lower.rs:1798). By-ref arguments
must now be plain variables — new E0438 with a "passing a field is not yet
supported" note. Covers function calls, method calls, and array elements,
on both sema implementations. Place-address lowering is the long-term fix
and stays in the epic.
Moves out of inout parameters (item 4): a callee could move out of an
inout param (fn steal(inout o: D) -> i32 { consume(o) }) with no
reinitialize-before-exit requirement, leaving the CALLER's variable
moved-from — a use-after-free shape that returned garbage today (observed
exit 127 for a 7+7 program). Any move out of an inout param is now
rejected flatly with E0437; the diagnostic documents the deliberately
conservative rule (reinitialization tracking may relax it later).
Field-level moves out of borrow params now also get the whole-var E0429
treatment. Inout forwarding (g(inout v) inside f(inout v)) keeps working
via an explicit byref-arg flag that replaces the old mark-then-unmove
hack — and composes with the loop re-move machinery (move-out in a loop
body errors; inout args in loops still compile).
Item 6 of the epic (Copy reads through a moved ancestor) was re-verified
still broken and remains tracked — the fix needs ancestor-moved checks on
Copy reads plus FieldSet auditing, too invasive to bundle here.
Fifteen CLI cases in byref_params.toml: the E0438/E0437/E0429 rejections
(including in-loop and reinit-then-return variants and a multi-file @import
case pinning the lazy path), plus controls for whole-var borrow/inout,
forwarding, reassign-only inout, and Copy field reads. Note E0438 was
renumbered from the worker's E0436 at integration: PR rue-language#930 takes E0436 for
DuplicateFunctionDefinition. Full test.sh green.
Part of RUE-127 (items 3, 4; remaining: 5, 6).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
…jectivity; drop-ordering pinned Three more items from the test-infrastructure-trust epic: Golden cases (item 6): a spec case combining golden-IR output with execution assertions ran only the golden comparison — its exit_code / expected_stdout were silently dead (reproduced: a case with correct expected_air, WRONG exit_code, and bogus expected_stdout passed). The harness now runs both: golden --emit comparisons first, then compile+run when execution assertions are present; compile_fail + golden-IR is rejected loudly as unsatisfiable. The dead "asm" header mapping is gone. One in-suite case (cfg_multiple_variables_drop_order) now exercises the combination live. ErrorCode injectivity (item 7): nothing guarded against two ErrorKind variants mapping to the same E-code. A new test parses the code() mapping and asserts every ErrorCode constant is claimed by exactly one variant — no duplicates, no orphans, no undeclared refs. This is timely: the two worker branches in this very cycle independently claimed E0436, which this test would have caught at queue-merge (renumbered to E0438 in rue-language#931). Drop-ordering (item 8): the drop-semantics spec cases asserted only exit codes, so they passed under fully INVERTED destruction order. They now pin order with @dbg destructor output: reverse declaration order ("2\n1\n"), per-branch drops, early-return drops, and declaration-order field drops (all verified against the real compiler). linear_copy_reverse_order_error tested nothing its name claims (no @copy in its source, compile_fail false); it now actually asserts the grammar rejects "linear @copy" in reverse order. Full test.sh green. Part of RUE-132 (items 6, 7, 8; remaining: bare compile_fail assertions, buck command_test targets, harness timeouts). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…mber merged Path patterns; one dup-symbol helper Three driver-layer fixes from the CLI-hardening epic: Stray intrinsic type arguments (item 8, silent miscompile): AstGen dropped IntrinsicArg::Type when lowering args for expression intrinsics, so @syscall(a, (), b) silently deleted the middle argument and shifted b into the wrong syscall register. The stray arg now lowers to a TypeConst placeholder and sema reports E0702 (intrinsic argument type mismatch) instead of miscompiling. CLI cases in intrinsic_args.toml. Rir::merge Path patterns (item 9): merge renumbered every InstRef except the module ref inside Path match-arm patterns, leaving refs from the second file onward pointing into the wrong file's instruction space. Now renumbered like everything else (preserving the u32::MAX None sentinel), with unit tests for both. The corruption is latent today — sema's enum resolution ignores the module ref — so a pinning multi-file CLI case documents current behavior rather than a user-visible repro. Duplicate-symbol detection (item 6): the same check was hand-written three times (merge_symbols, the parallel RIR path, and CompilationUnit::parse) and reported duplicate FUNCTIONS with DuplicateTypeDefinition. One detect_duplicate_symbols helper now serves all three sites, and functions get their own DuplicateFunctionDefinition error kind. Full test.sh green. Part of RUE-130 (items 6, 8, 9; remaining: 2-5, 7). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…itively The module system resolved imports only against files already passed on the command line — nothing in the pipeline ever read a resolved module file from disk. So `const utils = @import("utils")` failed with E0704 unless the user hand-listed every module, and the let-bound form resolved the path but called into a function table that never contained the module's functions ("undefined function"). The stdlib was unusable, full stop. The driver now runs a transitive discovery pass before compilation: each source's token stream is scanned for the `@import("...")` shape (token level, so comments and string contents can't confuse it), the path is resolved against the filesystem mirroring sema's ModulePath order — $RUE_STD_PATH/_std.rue then an adjacent std/ dir for "std"; exact path for "foo.rue"; {path}.rue then the _{basename}.rue facade for simple paths — relative to the importing file's directory and then the root source's, and each newly found file is loaded as if listed on the command line. Discovered files are scanned too (worklist), with canonicalized-path dedupe handling diamonds and cycles. Unresolvable imports are left for sema's existing E0704 with candidate context. Sema's ModulePath::Std arm was a hardcoded None ("not supported yet"); it now matches a loaded _std.rue facade, so `const std = @import("std")` resolves end-to-end and direct members work: std.abs(-5) returns 5 via either an adjacent std/ directory or RUE_STD_PATH. CLI cases: known_bug markers removed from the three module shapes this fixes (let-bound call, top-level-const call, directory facade — all now green), plus new cases for a transitive a->b import chain and direct std member access. The remaining gap is member access through a const module RE-EXPORT (pub const math = @import(...) — the std.math.abs chain, E0707): filed as RUE-136 with the two std-chain cases re-pointed at it. Full test.sh green. Fixes RUE-14 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…s for String and large aggregates The by-value aggregate calling convention disagreed with itself in three ways, each a silent miscompile (the RUE-106 epic's remaining children): - String returns: callers reserved an sret buffer but user-defined callees returned in registers and never wrote it — the caller read len=0 from its own untouched stack. Every String-returning function was broken (RUE-92). - Args past the register budget (6 integer slots on x86-64, 8 on aarch64): the caller pushed them on the stack, but the callee read every param from its frame param area, which the prologue populated only for register-passed slots — slots past the budget read zeros or ICEd (RUE-13, RUE-91), including multidimensional arrays and [T; 8]+ (RUE-79). - Aggregate returns wider than the return registers silently dropped slots (RUE-78's struct shapes). The convention is now explicit and documented at type_uses_sret_return and the ARG_REGS/RET_REGS definitions: - Arguments: flattened slots fill the register budget; the rest go on the caller's stack. The callee prologue allocates frame slots for ALL params and copies stack-passed slots down, so every body access is a uniform frame-slot load — the divergent over-budget branches in both cfg_lower files are deleted. This also fixes a latent spill-slot-below-rsp bug when param count exceeded the budget. - Returns: values fitting RET_REGS are unchanged; String (always) and over-budget aggregates use a caller-allocated sret buffer whose pointer rides as a hidden first argument. The callee pins it to a frame slot and stores all slots through it via the shared agg_slots::store_slots_to_sret. Both backends change together; aarch64 verified by asm inspection locally (CI's native arm64 job executes it). All six known_bug = "RUE-13" xfails in abi.toml now pass and are un-marked (regression tests), plus new cases for String returns (with and without args) and a 9-slot struct with stack args. Stress-verified: >6 scalar args, inout past the budget, 16-slot multidimensional pass+return, chained 11-slot sret calls. Full test.sh green. Fixes RUE-92 Fixes RUE-13 Fixes RUE-91 Fixes RUE-79 Fixes RUE-78 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…le), panic-hook ICE banner, full benchmark metrics The --emit pipeline diverged from normal builds in four user-visible ways (driver-hardening epic, items 2-5 and 7): - Module programs failed E0704 only under --emit: the emit path called the frontend without sema's file_id->path mapping. A new compile_frontend_from_ast_with_file_paths threads it through (the old entry point delegates with an empty map), so @import programs emit IR exactly like they build. - Warnings were silently dropped in every --emit mode: the frontend state's warnings were computed and discarded. They now print to stderr via the multi-file formatter, same as a normal build. - Multi-file --emit was impossible: with no -o, the legacy two-positional parse claimed the second FILE as the output path ("refusing to use 'b.rue' as the output path") — but --emit produces no executable. Under --emit every positional is now a source file. One parse_args unit test pinned the old dead-output behavior and is updated. - --benchmark-json source metrics measured only the first file; bytes, lines, and tokens now sum across all files. Also installs a panic hook (item 5): a compiler panic now prints an "internal compiler error: this is a bug in rue" banner with the version and the issue-tracker URL after the standard panic output, instead of ending on a bare backtrace pointer. Item 2's reported lex-error misattribution under --emit tokens no longer reproduces (file ids ride the tokens since the FileId threading fixes); covered by the multifile_errors cases. CLI: new emit_pipeline.toml cases pin warnings-under-emit and modules-under-emit; the harness gains compile_stderr_contains (positive mirror of the existing not_contains) to assert warnings on a successful compile. Full test.sh green. Part of RUE-130 (items 2-5, 7; remaining: item 6 landed in rue-language#930). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…ily) Sema computed move facts and discarded them before AIR; drop elaboration dropped every slot at scope exit regardless, so a value moved into a callee, into another local, or out via return had its destructor run TWICE — a double-free with heap types, masked today only by the no-op allocator (the RUE-108 epic, led by RUE-61). Move facts now flow into AIR as a passthrough MarkMoved instruction emitted at every whole-variable move site in both sema pipelines (builtin by-ref receivers cancel their marker; by-ref args emit nothing). CfgBuilder tracks moved slots path-sensitively — insert on MarkMoved; clear on reinitialization; if/match joins intersect the non-diverged branch states; loop and short-circuit edges restore conservatively — and skips the Drop (never the StorageDead) for moved slots. Callees now drop their owned by-value params at exit (new Air.param_drops, cleared for destructors to prevent self-recursion): the caller's spurious drop had been masking a callee-side leak, so suppression alone would have produced zero drops. Entirely AIR/CFG-level — zero per-backend codegen changes; both backends verified via asm (params reload from uniform frame slots). Verified by exact destructor output across: move-into-call, param-to-local, local chains, move-out-via-return, by-value method receivers, reassignment after move, array moves, borrow controls, early-return-in-branch, plus drop-exactly-once and LIFO-order controls. Eleven CLI cases in drop_moves.toml with exact @dbg stdout; three spec cases strengthened with expected_stdout (spec already mandates drop-exactly-once; no text changes). Honest residuals, pinned rather than papered over: partial FIELD moves (RUE-62) still re-drop the moved field via whole-struct glue — pinned with a known_bug case encoding the desired output; branch-divergent moves (moved in one arm only) conservatively keep the exit drop until runtime drop flags exist — pinned by unit test. Full test.sh green. Fixes RUE-61 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…rate specialization to fixpoint The comptime-generics epic's three roots, all repro-verified before/after: Argument type-check (the soundness hole): the inference generator's is_generic branch never added the equality constraint between argument and substituted parameter type that the non-generic branch adds. Scalars silently truncated (i64 passed where T=i32), bools coerced to ints, literals range-checked against i32 instead of T — and the aggregate case was memory-unsafe: passing struct A where T=B compiled and the callee read B-sized fields from an A-sized allocation (out-of-bounds read). A new extract_type_argument builds the substitution map (type literals, named struct/enum refs, forwarded type params; refuses shadowing locals) and the constraint is now added; a second check in sema's generic-call path compares each runtime argument's AIR type against the substituted parameter type, catching shapes inference can't see (type values held in locals). Both report the existing E0206. Symbol mangling: specialized names encoded Type::name(), which renders every struct as "<struct>" — ALL struct/enum instantiations of a generic collided on one symbol (duplicate-symbol link errors, or silently sharing one body). Mangling now encodes type identity; two instantiations link and get distinct bodies, verified by a layout-sensitive two-struct test. Forwarding: passing a comptime type param to a nested generic call ICEd at the CallGeneric arm (bare panic in the CFG builder). Specialization now iterates to fixpoint — each round scans newly created specializations for CallGeneric, rewrites, and queues — with dedupe via the existing map and a 64-round cap that reports a clean comptime-evaluation error instead of looping. The CFG panic remains as a now-unreachable guard. Frontend-only (rue-air); no backend mirroring needed. Ten CLI cases in generics.toml cover every fixed shape. Known cosmetic residual (pre-existing, tracked in the diagnostics epic): struct mismatches print "expected <struct>, found <struct>" — the unifier's wording for all struct mismatches repo-wide. Full test.sh green. Fixes RUE-99 Fixes RUE-100 Fixes RUE-102 Fixes RUE-73 Fixes RUE-101 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…ule forms are an error
Ratifies RUE-137: the directory-module facade lives INSIDE its directory
(utils/_utils.rue), matching the cited matklad article (his underscore file
is the mod.rs replacement — an earlier ADR revision misread "sorts first"
as parent-listing order) and the stdlib's existing std/_std.rue. Every
artifact that encoded the sibling layout is updated together:
- ADR-0026: design tree, rationale wording (sorts first WITHIN the
directory; self-contained modules instead of the self-contradicting
dual-file argument), a decision-history note, and resolution-order text.
- Driver discovery: simple imports probe {path}/_{basename}.rue.
- Sema: the in-directory facade is matched with a path-boundary check (a
stray sibling _utils.rue no longer masquerades as a directory module),
and get_module_identity loses its facade special case entirely — with
the facade in-directory, "module = parent directory" is the whole rule,
which also makes facade<->submodule private access work (it didn't,
exposed by the intra-directory spec case).
- Spec 4.13:82 reworded; CLI and spec cases moved to the new layout, plus
a negative case pinning that the sibling layout is not a module.
Along the way the dual resolver problem bit again: the LIVE import
resolver (analysis.rs) hand-rolled loaded-path matching and probed the
disk with its own (sibling) facade rule, separate from the structured
ModulePath the const path uses. Phase 1 now delegates to ModulePath — one
implementation — and the disk probe uses the ratified layout.
Also per review: when BOTH foo.rue and foo/_foo.rue exist, resolution no
longer silently prefers the file — it is a compile error, new E0708
"ambiguous module" (mirroring Rust's E0761), detected both among loaded
files and on disk; the driver loads both candidates so sema can report it.
The old "foo.rue takes precedence" spec case is replaced by new normative
rule 4.13:89 with a covering compile-fail test, and the stray-underscore
shape gets its own non-facade case. ErrorKind::AmbiguousModule is boxed to
respect the 64-byte ErrorKind budget.
Full test.sh green (traceability 100% including the new rule).
Fixes RUE-137
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
…egen, CFG, RIR, and parser The dead-code-as-correctness review theme (RUE-134): code that exists but does not run, sitting next to code that runs but is wrong. Eight items, net -1165 lines: - x86 Lea index/scale fields: liveness counted a use, regalloc never rewrote, emit silently dropped — removed end-to-end (every construction was None/1). - MovRMIndexed/MovMRIndexed virtual-operand emit arms contained plausible- looking code that pre-emission verification makes unreachable — now unreachable!() with the reason, mirroring aarch64's pattern. - regalloc SplitInfo: computed for every function, never read — deleted (SplitReason/SplitPoint/find_loop_split_points and friends). - BasicBlock::preds: the review said "zero consumers" but Cfg's Display and the destructor goldens consume it — the REAL bug was staleness after optimization. The stored field is gone; compute_predecessors() is now pure and computed on demand by Display (goldens unchanged), and the build-time call whose result was silently discarded is dropped. - TestEmitter (RUE-129 item 6): the drifted duplicate of the aarch64 mov-imm encoder is deleted; its five tests now run against the real encoder, expectations unchanged. - block_parser is now a thin map over maybe_unit_block_parser; the false "requires final expression" doc on both is corrected. - RIR ParamRef: kept (deleting touches ~15 rue-air match arms — separate change); documented, with a new test pinning that astgen never produces it. - FunctionSpan/RirFunctionView: dead API with an already-broken overlap invariant — deleted along with its producers and API-only tests. Full test.sh green on both passes (including the preds goldens). Part of RUE-134 (and closes RUE-129's TestEmitter item). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…rs that consume self
Two more children of the drop-elaboration epics, building on the MarkMoved
machinery:
Partial field moves (RUE-62): moving one field into a callee (eat(o.a))
still dropped the WHOLE struct — including the moved field — at scope
exit: a double-free with heap fields. MarkMoved now carries an optional
field index, sema exports single-level field moves on both pipelines, and
the CFG builder's move state went field-granular (whole slots + per-field,
intersected at joins; a projected write restores that field's drop). A
partially-moved struct's exit drop becomes: a plain CFG call to the
struct's own destructor (no glue — the existing Call path flattens struct
args, so no new codegen op on either backend) plus per-field drops in
declaration order, skipping moved fields. Verified by exact destructor
output: 800,1,777,2 — field a drops exactly once, inside the callee.
The known_bug = "RUE-62" case now passes and is un-marked.
Destructor self-consume (RUE-139): drop fn D(self) { consume(self) }
compiled and recursed at runtime (the callee's param drop re-enters the
destructor). Both destructor entry points now scan the analyzed body for
a surviving whole-value move of self and reject with new E0442 (Rust's
E0509 spirit); cancelled by-ref markers leave no trace, so borrowing
receivers stay legal.
Honest residuals, pinned: branch-divergent moves still conservatively
double-drop on the moving path (needs drop flags); nested partial moves
(o.a.b) keep the conservative whole-slot drop; destructors moving non-Copy
FIELDS out of self are not yet rejected (documented in code). Five new CLI
cases, one spec case on the existing drop paragraphs, three CFG unit
tests. Full test.sh green.
Fixes RUE-62
Fixes RUE-139
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
…arm-precise spans, E0600 names missing variants
Four citizen-quality items from the diagnostics epic, now more visible
since generics started type-checking aggregates:
- Type names (item 2): unify-error conversion and the sema error paths now
render named types via the type pool — "expected B, found A" instead of
"expected <struct>, found <struct>", "cannot match on type 'P'" instead
of '<struct>', with array types rendered structurally and integer-
literal inference vars as "{integer}". The dead-but-correct
type_mismatch_error helper is finally wired (the dead-code theme again).
- Direction (item 1): the general expected/found swap was fixed earlier;
the one remaining reversed arm was Array-vs-non-array in the unifier —
fn main() -> i32 { [1,2,3] } now reports "expected i32, found
[{integer}; 3]" instead of the reverse.
- Arm spans (item 3): a match arm whose result conflicts with the other
arms was reported against the WHOLE FUNCTION because an integer-literal
arm's unbound type var silently absorbed the conflicting arm; the
unifier now tracks int-literal vars through var chains and rejects
non-integer binds at the offending constraint — the error points at the
arm. One existing UI case had pinned the old whole-scrutinee span as a
documented regression; it now asserts the section's stated intent.
- E0600 (item 4): non-exhaustive match errors name what's missing via one
shared helper used by BOTH match analyzers ("missing variants: Green,
Blue"; "pattern `false` is not covered"; wildcard suggestion for
integers).
Thirteen new UI cases across type_names, type_mismatch_direction, and
match_diagnostics; full-string assertions where stable. Known limitation
recorded in the epic: fn f() -> [i32; 3] { 5 } still reports E0800 rather
than a mismatch (though it now names the array type). Full test.sh green.
Part of RUE-133 (items 1-4; remaining: 8-10 and the pre-existing silent
unknown-directive acceptance).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
… works The ADR-0026 re-export idiom — pub const math = @import(...) in a facade — was invisible to module member lookup: member access consulted functions, structs, and enums in the module's file, but never consts, so std.math.abs(-5) failed with E0707 "module 'std' has no member 'math'" while std.abs worked (RUE-136, the last gap from the RUE-14 split). Member access on a module type now also consults consts declared in the module's file (matched via the declaration span; visibility enforced like other members): a module-typed const yields its module, so chains resolve member-by-member. Value consts (pub const PI = ...) still fall through to the unknown-member error — const evaluation through the member path is a separate feature. Architecturally this is one edit in one place: member ACCESS is the single-copy path shared by both sema pipelines, and once the access types the receiver as a module, both member-CALL paths handle the chain unchanged. (The call pair is still duplicated — that dedup lands separately, after the in-flight worker branches that touch it.) The two known_bug = "RUE-136" CLI cases (std via adjacent dir and via RUE_STD_PATH, both using the std.math.abs chain) now pass and are un-marked; two new spec cases pin the re-export chain and that a non-pub re-export stays private. Full test.sh green. Fixes RUE-136 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…pipeline cure) analyze_module_member_call_ctx (lazy pipeline) and analyze_module_member_call_impl (eager pipeline) were ~100-line hand-mirrored copies — and had already drifted: the eager copy skipped the exclusive-access check entirely. The shared body — visibility, arity, and argument-mode checking, plus the call-args encoding and Call emission — now lives once as check_module_member_call / emit_module_member_call. Each pipeline's wrapper shrinks to its genuinely pipeline-specific seams: function lookup (same map, two access paths), argument analysis (which recurses into the owning pipeline), and the exclusive-access check. The eager wrapper now runs its exclusive-access check (the drift is corrected in the direction of checking more; the path is only reachable through module values, which require imports and thus normally take the lazy pipeline — so no user-visible behavior changes, confirmed by the full suite). Two pre-existing holes found while extracting, verified by repro and filed rather than silently changed: - RUE-140: member calls resolve ANY global pub function, never checking the function belongs to the receiver module's file — utils.sneaky() happily calls other.rue's sneaky(). The shared body is now the single fix site. - RUE-141: the two pipelines' exclusive-access checks have drifted (the lazy one misses borrow duplicates and lvalue checks) — left as a per-pipeline seam with comments pointing at the issue. Behavior-preserving otherwise; full test.sh green. Part of the dual-pipeline unification (RUE-134 family; relates RUE-120). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
… weak-symbol semantics Two more items from the linker-correctness epic: Arch validation (item 10): ObjectFile parsed the ELF machine type and then discarded it — the linker would happily combine objects of different architectures, confirmed in the review as an "aarch64" output containing 22 x86 syscalls. ObjectFile now records its machine (the Mach-O parser only accepts ARM64 and tags accordingly), and add_object refuses objects whose architecture doesn't match the link target with a new ArchMismatch error. Strengthens the RUE-36 cross-compilation story: a wrong-arch runtime archive now fails loudly at link instead of producing a binary that dies on the first instruction. Weak symbols (item 9): two fixes to match standard ELF semantics. Among multiple WEAK definitions the FIRST now wins (the old code replaced an existing weak with any later definition, weak included); a strong definition still overrides a weak one in either order, and two strongs still collide. And a relocation against an UNDEFINED weak symbol now resolves to address 0 instead of an undefined-symbol error — the standard optional-symbol idiom. Three new unit tests: arch mismatch rejected; the weak ordering matrix (weak/weak keeps first, weak/strong overrides, strong/weak keeps strong); and an end-to-end link asserting the patched imm64 for an undefined weak is 0. Full test.sh green. Part of RUE-131 (items 9, 10; remaining: 2-8, 11 — the flat symbol map and the Mach-O layout family). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…reject malformed relocations Three Mach-O items from the linker epic, plus a fourth bug found while fixing them: __TEXT sizing (item 3): the segment's file size was hardcoded to exactly one 16KB page. Any program whose headers + code exceeded that had its code spill past the declared segment — the data segment then overlapped it in the file and dyld mapped garbage. The segment now grows to the page boundary after the code ends, and __DATA starts where __TEXT actually ends. Layout drift (found during item 3): the relocation pre-pass (calculate_text_file_offset_for_dynamic) and build_dynamic computed the layout INDEPENDENTLY and had drifted — the pre-pass counted one data section where the builder counts up to three (__const/__data/__bss), so with more than one data section every relocation was patched against a text offset the binary didn't use. Both now consume one compute_dynamic_layout, and a test pins their equality with all three sections present. (The dual-implementation disease, linker edition.) bss addresses (item 4): link_macho computed bss symbol vaddrs as data_vaddr + aligned data length + bss_offset_in_data — but bss_offset_in_data IS the data length, so every bss symbol pointed one data-length past its storage. The double-count is removed. Malformed relocations (item 6): a non-extern relocation with r_symbolnum == 0 underflowed (r_symbolnum - 1) and indexed out of bounds — a parser panic on malformed input. Now a clean ParseError. The first push of this change FAILED macOS CI with garbage string output — its own medicine: the unified computation ran before build_dynamic pushed __LINKEDIT, while the pre-pass builder had it pre-added, so the two disagreed by one segment header and every relocation was patched for an offset the binary didn't use. The shared computation now counts __LINKEDIT conditionally (present-or-added), and a regression test mirrors the linker's exact asymmetric usage: pre-pass builder with manual __LINKEDIT versus build builder without, asserting the offsets agree. Three new layout tests (the >1-page growth invariant, the precompute/build equality with all data sections, and the asymmetric pre-pass/build agreement) plus the suite. Mach-O execution is validated by CI's macos job; layout invariants are pinned at the unit level here. Part of RUE-131 (items 3, 4, 6 + the layout drift; remaining: 2, 5, 7, 8 Mach-O-side, 11). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…ss check for both pipelines Two soundness fixes on top of the member-call dedup: Membership (RUE-140): member calls looked the function up in the GLOBAL table and checked only visibility — utils.sneaky() resolved to other.rue's pub fn and returned 99. The shared call body now takes the receiver module's file (threaded from the receiver's module id in both pipeline wrappers) and rejects callees defined elsewhere with E0707, the same module-scoped error member access uses; unknown members also report E0707 instead of a generic undefined-function. New spec legality rule 4.13:90 with a covering case, plus four CLI cases (the rejection, a same-module control, the facade re-export chain, and facade/inner non-membership). Exclusive access (RUE-141): the eager pipeline's check (inout duplicates, borrow/inout conflicts, lvalue-ness) and the lazy pipeline's (inout duplicates only) had drifted. Honesty note from verification: the issue's claimed regular-call repro was FALSE — regular calls in the lazy pipeline route through the eager methods, and the drifted _ctx check was reachable only via the dead parallel pipeline. The drift WAS user-visible through module member calls on trunk: m.swap(inout x, inout x) and m.observe(borrow x, inout x) both compiled (verified against the old binary). There is now ONE implementation with the eager semantics (check_exclusive_access_in); both former checks are thin adapters, the drifted _ctx variable extractor is deleted, and four CLI cases pin the previously-accepted shapes as errors. Full test.sh green; traceability 100%. Fixes RUE-140 Fixes RUE-141 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…ough moved ancestors The last two children of the move-checking epic: Linear may-move (item 5): must-consume read the same union-at-joins state that use-after-move uses, so a linear value consumed in only ONE branch passed the check — and match arms never saved/restored move state at all. Move state now tracks consumed-on-all-paths separately (intersected at joins; one-sided branches merge against the pre-branch state; match arms analyze from the pre-match state and join properly in both pipelines; diverging branches count as consuming). New E0443 "linear value is not consumed on all paths" with a consumed-here label. Side benefit: consuming a value in two DIFFERENT match arms is no longer a false use-after-move. Copy through moved ancestor (item 6): let a = consume(o.f); let b = o.f.x; was accepted — reading a Copy field through a moved ancestor, a use-after-free now that drops are move-aware. The Copy-field branch of field access checks every ancestor prefix in both pipelines, rejecting with the existing E0205. The reassignment control exposed a pre-existing hole — field ASSIGNMENT never cleared moved state — fixed alongside (index-projection places excluded to stay sound). Both compose with the loop back-edge recheck and the field-granular drop state, pinned by in-loop variants. New spec rules 3.8:50-56 with eleven covering cases (traceability 100%), fourteen CLI cases, four unit tests. Full test.sh green. Fixes RUE-127 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…ented system The entire module system's normative spec was ten paragraphs inside the @import intrinsic's section. This adds chapter 10 (docs/spec/src/10-modules/, five sections plus index) covering what is implemented and verified today — every rule and example was run against the compiler before being pinned: - Module forms: file modules and directory modules with the in-directory facade (foo/_foo.rue, per the RUE-137 ratification), the sibling form's non-meaning, and the E0708 dual-form ambiguity (referencing 4.13:89). - Import resolution: full candidate order including std via $RUE_STD_PATH then adjacent std/, importer-relative before root-relative, transitive disk loading, and load-once cycle dedupe (cycles are legal). - Visibility: pub is cross-directory; non-pub items are visible within their directory, facade included; re-export chains through pub const, and non-pub re-exports staying private. - Module bindings: const (top-level) and let (local) binding forms and their equivalence; modules are not runtime values (argument, field, and comparison positions error). - Program composition: the flat-namespace duplicate-symbol rule, pinned with an informative note marking it transitional. The existing 4.13:79-90 intrinsic rules are referenced, not moved — consolidation is a separate change. Sixteen new spec cases across three new case files plus annotations on ~20 existing module cases; normative traceability stays 100% (539/539, up from 513). Two incoherent behaviors were deliberately left unpinned and documented in the issue: member-call results in arithmetic positions, and module expressions in unit positions. Fixes RUE-138 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…on every path The last structural piece of the drop-elaboration epic. A value moved on only SOME paths (one if/match arm, a break-guarded loop move, a short-circuit edge) is not "moved on all paths", so the static analysis must keep its scope-exit drop — which double-dropped on the moving path. This was the documented, test-pinned residual of the move-suppression work. The CFG builder now maintains runtime drop flags, Rust-style: a pre-scan finds every slot with a whole-value move marker anywhere in the function; each such droppable slot gets a hidden i32 frame slot (via the existing temp-local allocator, so both backends see the grown frame through cfg.num_locals with zero codegen changes). The flag is armed (1) at the value's initialization and every reinitialization — Alloc, Store, ParamStore, whole-place writes, and function entry for owned params — and cleared (0) at each move marker. Scope-exit and param drops for flagged slots are emitted behind an `if flag != 0` diamond. Because the flag tracks the EXECUTED path, this corrects every shape the static intersection is conservative about, not just if/match divergence: the loop-with-break move and short-circuit edges also drop exactly once now. Statically-decided cases keep their existing zero-cost handling (moved-on-all-paths drops stay elided outright; never-moved slots get no flag and no guard). The unit test that pinned the double-drop as a documented residual now asserts the guarded shape instead. Five CLI cases pin exact destructor output across both paths of if and match divergence, the loop-break move, re-arming on reassignment after a divergent move, and owned-param divergence. Remaining epic residual (noted, unchanged): nested partial moves (o.a.b) keep the conservative whole-slot behavior. Full test.sh green twice. Fixes RUE-108 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…trict Two quick wins from a buck2 setup review: .buckconfig gains [project] ignore = .claude, .jj. Agent worktrees under .claude/worktrees each carry a full repo copy WITH its own buck-out, all inside the buck2 project root: their vendored prelude BUCK files broke every `//...` target pattern outright (uquery choked parsing a worktree's bundled prelude), and their constant churn streamed file-change events into the root daemon, invalidating its in-memory graph — the likely root of the recurring "0% cache hits" rebuilds. .jj's op store writes on every command in this colocated repo and is cheap insurance. Verified: `//...` patterns resolve again with the ignore in place. find_rue_binary loses its guessing fallback. When RUE_BINARY was unset it globbed every buck-out config-hash directory and picked the NEWEST compiler binary by mtime — with debug, release, and historical configs side by side that silently selected the wrong compiler (a real incident this week: a just-edited suite ran against a mid-rebuild binary and reported false failures). Resolution is now: RUE_BINARY, then the bin/rue symlink scripts/rue build maintains, then a loud error telling you to set one — never a guess. Full test.sh green (it always sets RUE_BINARY, so behavior there is unchanged; the strictness only bites formerly-silently-wrong ad-hoc runs). The remaining review findings (BUCK macro dedup, toolchain-level deny_lints, rue-runtime's nine never-compiled test modules, the inert CI buck cache, sh_test promotion, stale-config GC) are tracked in Linear. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Plans the cure for the codebase's most expensive structural problem: semantic analysis exists as three parallel implementations (eager Sema methods, the lazy _ctx family, and a dead #[allow(dead_code)] parallel pipeline), and the live two have drifted repeatedly — five confirmed instances are catalogued, including "same program, different verdict depending on whether it has an import" (RUE-141). Proposes option B: delete the dead parallel pipeline, run a systematic differential audit of every _ctx/method twin (differences become bug fixes with pinning tests, landed separately), re-point the sequential lazy driver at the Sema methods cluster by cluster deleting _ctx functions as their callers disappear, and collapse SemaContext to what the lazy scheduler actually needs. Each phase independently shippable and suite-verified; diagnostics changes forbidden during the mechanical phase. The two completed dedup cuts (rue-language#943 member calls, rue-language#946 exclusive access) are the proven pattern this scales up. Doc only — no code changes. Part of RUE-134. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Pure, behavior-preserving refactor. analysis.rs was one 7736-line `impl Sema` (3.5x the next-largest file), where most future language work happens. Split the impl into 9 focused submodules under sema/analysis/ (anon_methods, builtin_ops, calls, functions, instructions, intrinsics, ownership, pointers, type_inference), each a verbatim `impl Sema` block; the root drops to 1510 lines (free fns + enum + shared helpers + mod decls). Largest new module is intrinsics.rs at 1312 lines. Only non-move edits are mechanical: private -> pub(super) on moved methods for cross-submodule resolution, super:: -> super::super:: for sema-relative paths in moved bodies, and widening the consistency-test include_str! scan to the whole analysis/ tree. No logic, error-code, or behavior change — function bodies moved verbatim. clippy clean; test.sh Pass 24, 100% normative traceability (697/697), 342 rue-air unit tests pass. (The source-scanning consistency tests failed right after the split, then passed once the include set widened — confirming the safety net actually exercises the moved arms.) Fixes RUE-4 Co-Authored-By: Claude <noreply@anthropic.com>
…g enums (RUE-348 miscompile)
From the rue-cfg code review. A real SILENT MISCOMPILE at -O1+ (the default opt
level): fold_enum_comparison folded an enum ==/!= using only the variant index
(get_enum_variant dropped payload_len), so two operands of the same variant with
different payloads folded to "equal". Sema allows ==/!= on any enum structurally
(RUE-285) and codegen compares tag+payload via emit_aggregate_equality, so -O0
and -O1 disagreed:
enum Opt { None, Some(i32) }
if Opt::Some(5) == Opt::Some(6) { 1 } else { 0 } // -O0: 0 (correct), -O1: 1 (WRONG)
Fix: get_enum_variant now returns payload_len; fold_enum_comparison only folds
when BOTH operands are payload-less (payload_len == 0). Fieldless-enum folding
(the @target_arch() == Arch::X86_64 case) stays; payload-carrying comparisons
fall through to codegen's structural equality. Verified: Some(5)==Some(6) now
exits 0 at -O0/-O1/-O2; Some(5)==Some(5) exits 1. Added a differential_opt
regression case (compiled + run across all opt levels, asserts agreement).
Also fixed a misleading comment in fold_shift: a shift amount >= the bit width is
masked mod the width (spec 4.3a:10), not UB — the fold still defers the masked
case to the backend (which masks correctly), but the comment no longer
contradicts the spec + dce.rs.
clippy clean; test.sh Pass 24, 100% traceability, oracle-diff 0.
Fixes RUE-348
Co-Authored-By: Claude <noreply@anthropic.com>
…atements prose pass - RUE-344: the arena allocator sized fresh arenas as align_up(header_size + min_size, 4096), ignoring that alloc_from_arena places the block at align_up(header_size, align) — for align > 8 that padding was uncounted and the fast path has no bounds check, so alloc(65512, 16) returned a block 8 bytes past the mmap region. Threaded `align` into alloc_arena (size includes the aligned offset + full size); fixed the false SAFETY comment. 2 boundary unit tests (align 1..4096). Not codegen-reachable today (only align 1/8 emitted); the surface is the public __rue_alloc ABI. - RUE-323 (ADR-0043 Phase 2): the bulk Vec→ArrayBuf rename already landed (62de487); cleaned up the 5 stragglers it missed (spec 9.2:9 + 4 CLI case comments). Verified ArrayBuf(i32) runs; Vec(i32) cleanly errors E0202. - RUE-299: filled the remaining documenting-existing-behavior spec gaps (source encoding 2.0:5/6, never-propagation 3.4:6a, let shadowing/wildcard 5.1:14/16, items taxonomy 6.0:1a, field/method name spaces 6.4:33, const-eval trapping 6.5:12/14, @dbg format 4.13:8a), each verified by execution; 16 spec cases. The two design-gated remainders (duplicate-param-name policy, reserved-type-name table) are split out / tracked elsewhere. - RUE-202 (statements chapter): retired folk-terms in ch5 against the formal core — value-denotation for statements (type (), no value), assignment as a store into a place with overwrite-drop, RHS-first eval — cited to core §5.2/§6.7/§6.8. No normative change; verified by execution. clippy clean; test.sh Pass 24, 100% normative traceability (705/705), oracle 0. Fixes RUE-344 Fixes RUE-323 Fixes RUE-299 Co-Authored-By: Claude <noreply@anthropic.com>
… struct (RUE-322) The ADR-0043 Phase-1 slice runtime — the keystone that blocks the entire str/StrBuf chain (RUE-324..327). Three prior workers judged the 2-word fat-pointer ABI a multi-session codegen effort; the winning insight is that a second-class slice is *just* a 2-field @copy struct {ptr: @raw, len: u64} desugared onto existing intrinsics — so all three documented ABI blockers collapse into ZERO new codegen and ZERO new MIR variants, working on both backends for free. Implemented (all in rue-air, under --preview slices): - `[T]` becomes a synthetic 2-field struct {ptr, len}; slice params pass by-value through the existing multi-slot struct ABI. - array→slice coercion (HM inference relaxed); `borrow arr` (a [T;N]) desugars to {@raw(arr[0]), N}. - `s.len()` → the len field read; `s[i]` → `@assert(i < len)` + `@ptr_read(@ptr_offset(ptr, i))` (runtime bounds-checked, trapping); slice-param forwarding. - retired the E0486 not-yet-implemented gate for the slice-param position; un-marked both slices_mvp xfails; fixed the stale E0486 CLI/UI cases. Verified by execution: sum(borrow a) over [i32;3] = 60; OOB s[i] traps (exit 101); s[1]=5, .len()=6, [u8] stride correct, forwarding=60, element-type mismatch → clean E0206. aarch64 via --emit asm: correct bounds-check + ptr sequence, no ICE. clippy clean; test.sh Pass 24, 100% traceability (705/705), oracle-diff 300 seeds 0 disagreements. Deferred (follow-ups): inout write-slices, range slicing x[a..b], ArrayBuf/Vec slices. Part of RUE-322 Co-Authored-By: Claude <noreply@anthropic.com>
…l & lazy-analysis ADRs - RUE-132 (harness robustness/correctness, Part of): spec expected_stdout now compares byte-exact (strips only the TOML """ boundary newline, not per-line trailing whitespace / internal blanks) so a stdout regression can't pass the spec suite while failing the CLI runner; differential_opt cases missing an explicit stdout/exit_code are rejected at load; a case with error_contains but no compile_fail (silently never checked) now panics at load; run_with_timeout drains stdout+stderr on reader threads (no >64KB deadlock, RUE-338 class); the golden-IR --emit path runs under the timeout. 13 new tests. - RUE-333: form-feed (U+000C) is no longer skipped as whitespace, resolving the lexer-vs-spec drift (2.3:1 lists only space/tab/nl/cr). Research confirmed the interesting split Steve flagged: Rust treats FF as whitespace (verified by running rustc), Zig does not — matched spec + Zig (strict). A form-feed source now errors cleanly (E0001) instead of silently compiling. - RUE-245 (Part of): ADR-0044 optimization-levels — researched GCC/Clang/rustc/ Zig level conventions; proposes Rue's -O0/-O1/-O2/-O3 contract centered on the observable-behavior-identical-across-levels invariant (enforced by the RUE-236 differential harness), with -O2/-O3 honestly aliasing -O1 today. Docs only. - RUE-328 (Part of): ADR-0045 lazy-semantic-analysis — documents the decided Zig-style compile-on-reference model (conditional-compilation-for-free, the unreferenced-decl tradeoff, incremental module-loading rollout). Docs only; implementation to be broken into tasks next. clippy clean; test.sh Pass 25, 100% normative traceability (705/705), oracle 0. Fixes RUE-333 Co-Authored-By: Claude <noreply@anthropic.com>
… pass - RUE-181 (Part of): ADR-0046 "Delete Flat Multi-File Mode" (Accepted) — designs the warn→error migration to require @import for all cross-file refs, recommends explicit-@import (no auto-import, don't block on prelude/rue.toml), main.rue as an ordinary module, and measures the blast radius (~36 flat-mode CLI cases / ~18 spec cases). Docs only; implementation tasks follow. - RUE-19 (Part of, items 2-4): use-after-move E0205 now ends with a copy-pasteable `help: pass it by borrow instead: \`borrow x\`` (all 8 sites); `impl Block {}` gets "Rue has no impl blocks; define methods inside the struct body"; fixed the occurs-check <struct> name leak. Item 3 (duplicate inout-self error) refuted — already deduped. - RUE-202 (Part of, expressions chapter): retired folk-terms in the operator sections (4.2 arithmetic, 4.3 comparison, 4.3a bitwise, 4.4 logical) against the formal core (§5.4/§5.8/§6.4), verified by execution (overflow/div-zero/MIN÷-1 trap; shift-modulo-width; structural == leaves operands usable). No normative change. clippy clean; test.sh Pass 25, 100% normative traceability (705/705), oracle 0. Co-Authored-By: Claude <noreply@anthropic.com>
… warn on --log-format without --log-level From the rue-compiler code review. Three driver UX fixes in the rue CLI: - --help listed only 7 of the 11 supported --emit stages (omitting lowering, liveness, regalloc, stackframe) — a user hitting a typo saw all 11 via the error path, but --help disagreed. Now derives the list from EmitStage::all_names() (the single source of truth). - --emit combined with --benchmark-json corrupted stdout (IR text + benchmark JSON on the same stream, making it unparseable). The normal compile path already suppresses its 'Compiled ...' line under --benchmark-json for exactly this reason; the emit path never did. Now rejected early with a clear error. - --log-format without --log-level was a silent no-op (logging off by default, so the format never applied). Now warns. (-j ignored in --emit mode → RUE-352; the .rue-only clobber guard → RUE-351 — both filed rather than rushed.) clippy clean; test.sh Pass 25. Co-Authored-By: Claude <noreply@anthropic.com>
- RUE-324 (ADR-0043 Phase 3): `str` type CORE — a {ptr,len} view reusing the
slice representation (gated --preview string_trio), static-backed first-class
string literals in .rodata, `.len()` (byte length), bounds-checked byte-index
`s[i]` (traps 101 on OOB), and first-class use as param/return/field/reassign.
Key correction: slice `s[i]` strides by slot_count*8 (right for 8-byte-slotted
array elements, WRONG for packed rodata bytes) — str indexing routes through a
new packed-byte runtime `__rue_str_byte_at`. Both backends verified. Deferred
(clean errors, no soundness gap): str concat/compare/interop/char-iter, range
slicing, second-class borrow str.
- RUE-132 (compile_fail honesty, completes the epic): a load-time guard now
requires every compile_fail case to assert its error (both harnesses), and the
106 bare spec cases (28%) were backfilled with their real E-code. Found +
fixed a deeper bug: expand_case silently dropped per-param error assertions, so
the inout_exclusive_access "same variable" check had never run. Traceability is
now honest — skipped/preview-allowed-to-fail tests no longer count as coverage,
exposing 5 normative rules covered only by skipped @allow-directive tests (a
gated self-clearing allowlist; 99.3% real). Marked the str cases
preview_should_pass so implemented-but-preview-gated rules count correctly.
- RUE-202 (types chapter): retired folk-terms in 3.4 never/coercion (transfers
control away; (Sub-Never) subsumption) and 3.9 destructors (drop glue; drop-once
= moved-out-skip → no double-free), cited to the core.
clippy clean; test.sh Pass 25, honest traceability 99.3% (705/710, 5 allowlisted),
oracle-diff 0.
Fixes RUE-324
Fixes RUE-132
Co-Authored-By: Claude <noreply@anthropic.com>
…e) form The E0102 message for a Rust-style `as` cast suggested '@intcast(T, value)' — a two-argument form that does not exist: @intcast takes ONE argument (the value; the target type comes from context / the binding), so following the hint gave a second error (E0701 'expects 1 argument, found 2'). Spec 9.2 confirms the 1-arg form (@intcast(got)); @cast is not a real intrinsic (E0700). Corrected the hint to '@intcast(value)', which now compiles + runs. Found while capturing agent-ergonomics data (RUE-353): the coding agent kept reaching for `as` (huge training-corpus prior), and the error's own suggested fix then misled it a second time. The UI test pins the '@intcast' substring, so it stays green. clippy clean; test.sh Pass 25. Part of RUE-353 Co-Authored-By: Claude <noreply@anthropic.com>
…rose
- RUE-349: duplicate parameter names were silently accepted (first-wins). New
E0491 DuplicateParameter at sema (check_duplicate_param_names in the FnDecl
arm — covers free fns, methods, assoc fns, anon-struct methods), pointing at
the second occurrence (added a span to RirParam). `self` + distinct params is
fine. Spec rule 6.1:34 + 4 spec / 5 UI / 4 CLI cases.
- RUE-352: -j/--jobs was ignored in --emit mode. Hoisted the rayon-pool config
to configure_thread_pool(jobs), called once in main() before the emit/compile
dispatch, and removed it from compile_multi_file_with_options (build_global
panics on a second call). --emit -j1/-j2 now honored.
- RUE-351: the output-clobber guard only matched a .rue suffix. Now compares
resolved paths (clobber_key), so `rue prog -o prog` / `rue ./prog -o prog` /
`rue prog -o sub/../prog` all error with the source intact; `rue a.rue -o out`
still compiles.
- RUE-202 (chapter 8 runtime behavior): retired folk-terms ("can fail",
"detected", "cause a runtime panic") into the core's exact trap vocabulary
(the four trap categories, (Panic-Lift), fixed exit 101), cited to §6.4/6.5/6.12.
clippy clean; test.sh Pass 25, honest traceability 99.3% (706/711, 5 allowlisted).
Fixes RUE-349
Fixes RUE-351
Fixes RUE-352
Co-Authored-By: Claude <noreply@anthropic.com>
From the rue-builtins code review (runtime-ABI + definition consistency): - test_all_string_methods_present omitted `contains` and `starts_with`, so dropping either declared method (both have runtime backing) would have left the runtime symbol orphaned with the guard test still green. Added both. - The "how to add a builtin" Vec worked-example called __rue_free(ptr, size) — the real runtime signature is __rue_free(ptr, size, align) (3 args). Fixed the exemplar so a future StrBuf/ArrayBuf destructor implementer targets the right ABI. - The freestanding-ops block claimed to be the "single source of truth" for directly-lowered runtime symbols but omitted String/str byte-indexing and chars(); narrowed the comment and pointed at them. (Two needs-decision findings filed to Backlog: RUE-354 reserved-name guard misses the runtime's memcpy/memset/_main; RUE-355 bool-helper return-register cleanliness.) clippy clean; test.sh Pass 25. Co-Authored-By: Claude <noreply@anthropic.com>
- RUE-326 (ADR-0043 Phase 5): the `Str(N)` fixed string rung. Reuses `str`'s
2-word {ptr,len} representation via a new is_str_like unifier, so NO codegen
changes on either backend. Supports: literal-fits construction (an over-long
literal is a clean compile error, E0492 — renumbered from the worker's E0491,
which RUE-349 had taken for DuplicateParameter), `.len()` (current byte length
≤ N), bounds-checked byte-index `s[i]`, and coercion/borrow to a `str` view.
New rules 3.7:49-55; 11 spec + 8 CLI cases (preview_should_pass). Verified by
execution: Str(8)="hello" -> len 5, s[0]=104; Str(3)="hello" -> E0492;
borrow-to-str, multibyte, OOB trap, round-trips all pass.
Model note: followed the CAPACITY model (Str(N) holds up to N bytes, len ≤ N),
the string analog of [u8; N]. Deferred (noted): mutation/append, and genuine
inline-[u8;N] storage (construction is static-backed rodata today —
observationally identical for the immutable literal-only surface, since Rue
arrays are 8-byte-slotted and a packed inline buffer is a larger codegen task).
- RUE-202 (items chapter 6): retired folk-terms in constants (6.5 — comptime-set
membership 4.14:26-29), enums (6.3 — compile-time error, E0421/E0420), and
structs (6.2 — mutable field as an assignable place, 5.2). No normative change.
clippy clean; test.sh Pass 25, honest traceability 99.3% (713/718, 5 allowlisted),
oracle 0.
Fixes RUE-326
Part of RUE-321
Co-Authored-By: Claude <noreply@anthropic.com>
…ated alias (RUE-325 increment) ADR-0043 Phase 4 green increment: StrBuf is now the canonical built-in name (display, dispatch, sret check on both backends, all diagnostics); String remains a silent deprecated source-level alias resolving to the same synthetic struct, so all ~249 existing String sites keep compiling. Runtime symbols stay __rue_String_* (internal). Fixed two alias-induced invariants: pool/registry by-name aliasing, and all_struct_ids de-dup (a drop-glue double-generation hazard). Integration: renumbered the informative alias rule to 3.7:57 (the worker's 3.7:49 collided with Str(N) rules 3.7:49-55 that landed in rue-language#1162 after its base). Verified by execution on the merged tree: StrBuf new/push_str/len/println/ @to_string/concat (exact stdout), String-alias parity exit 0, struct StrBuf E0404, aarch64 --emit asm sret path, and a cross-mechanism Str(N) x StrBuf program (5 / 104 / hello / exit 0). ./test.sh exit 0. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…ass) Precision-only rewrite of docs/spec/src/07-arrays/01-fixed-arrays.md: array literals get value-denotation + ownership (cites (Array-Intro) §5.8, (D-Array) §6.5); indexing denotes the element place, value context yields copy/move (cites (D-Index) §6.5); bounds rules state the exact check point and the trap (i negative or i >= n, exit 101; cites (D-Index-Trap) §6.5, §6.12, ch 8.2). No requirement changes; rule IDs and categories stable. Avoided 7.1:43-48 (RUE-299) and the 7.1:25-31 move-legality prose. Verified by execution: arr[1] -> exit 42; runtime OOB -> "error: index out of bounds", exit 101. ./test.sh exit 0, traceability baseline unchanged. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…ity, stale comment Four verified findings from the rue-rir component review (2026-07-04): - gen_for anchors the @__rue_iter_len intrinsic (and its collection VarRef) on the ITERABLE's span, so the not-iterable E0206 underlines the offending expression instead of the whole for statement. UI case pins 3:14. - The --emit rir dump no longer hides load-bearing fields: Alloc prints [iter_elem], InfiniteLoop prints "borrows <coll>", and FnDecl/ConstDecl/Alloc print their directives like StructDecl already did (shared format_directives helper, StructDecl deduplicated onto it). - @copy with arguments is now a compile error ("@copy takes no arguments", spec 2.5) instead of silently accepted-and-ignored; @Allow args unaffected. UI case added. - PARAM_SIZE doc comment corrected to the real 7-word layout (was stale at 4, missing is_comptime + the 3 span words). Verified by execution: E0206 caret at 3:14 under notiter; @copy(garbage) errors; bare @copy + @Allow(unused_variable) still work (exit 10, no warnings); rir dump shows [iter_elem]/borrows xs. clippy clean, ./test.sh exit 0. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…o-case guard, arity guards Four verified findings from the rue-oracle component review (2026-07-04): - ZST parameter slots (real oracle bug): the compiler gives zero-sized types ZERO ABI slots (abi_slot_count) but still emits a Param read for them at the NEXT param's index; the oracle forced every arg to >=1 slot, so any fn with a ZST param before a value param made the oracle read the wrong slot and report a phantom DISAGREE blaming codegen. Fixed with a type-aware Param arm (is_zero_sized/zero_sized_value over type_pool) + zero-width arg layout; regression tests incl. two-level forwarding. The naive fix (drop .max(1)) was insufficient — the CFG shares the ZST's index with the next param, found via --emit cfg during integration. - Spec-mode stdout compared with normalize_golden, which trims trailing whitespace/boundary blank lines — a whitespace-shaped oracle-vs-compiler divergence scored AGREE. Now byte-exact modulo the single """ boundary newline via rue_test_runner::strip_block_boundary_newlines (made pub), matching the spec runner and corpus mode. - finish_report returned SUCCESS with zero cases checked (existing-but-empty dir, TOMLs with no parseable cases) — a corpus-wiring regression would turn CI green while testing nothing. total==0 is now FAILURE. - string_builtin arity + type guards: a runtime-fn signature drift (the RUE-314 class) now yields an honest Unsupported skip instead of reading args positionally from wrong slots / coercing non-strings to "". Also fixed the inverted over-shift comment in the fuzz generator (shifts mask, never trap). Verified by execution: ZST probe DISAGREE->agree; ws-divergence spec case AGREE->DISAGREE; empty/no-case dirs exit 0->1; corpus and spec tallies byte-identical to baseline (498/78/518 and 1259/94/571, 0 disagreements) so the guards introduced zero false skips. clippy clean, ./test.sh exit 0. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…lently ignoring/false-clobbering Two verified findings from the driver component review (2026-07-04): - Broken pipe: Rust startup ignores SIGPIPE, so `rue ... | head` made every stdout write panic and the RUE-130 panic hook printed the full "internal compiler error, please report this" banner (exit 134) for a benign condition. main() now restores SIG_DFL on Unix: the process exits silently with SIGPIPE (141) like every other CLI in a pipeline. - --emit mode never writes the output path, but -o was both silently ignored (no file, no message — `--emit asm a.rue -o out.s` leaves the user waiting for out.s) and unconditionally clobber-guarded (`--emit ast a.rue -o a.rue` falsely refused). The guard now applies only to executable-producing mode, and an explicit -o under --emit warns that IR goes to stdout. Unit test pins both sides. Verified by execution: pipe to head -0 -> status 141, no banner; --emit with -o -> warning, exit 0, no file created; --emit -o <source> accepted; plain -o <source> still refused; normal compile unaffected. clippy clean, ./test.sh exit 0. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…+ CLI compile timeout + shared ice_message Six verified findings from the test-harness component review (2026-07-04) — the "next RUE-132" sweep. Four are gate-integrity holes where a case that runs nowhere (or is allowed to fail) still counted as spec coverage: - Duplicate rule ids: parse_spec_file was last-writer-wins, so an informative restatement authored after a normative rule silently ERASED the coverage requirement (the near-miss 3.7:49 incident). Duplicates are now collected, printed, and fail the gate. - Spaced Zola shortcode args: `cat = "normative"` (spaces) silently demoted a rule to informative and `id = "..."` made it invisible — Zola renders both fine. The hand-rolled arg extraction is now whitespace-tolerant. - only_on platform typos: `only_on = ["x86-64-linx"]` skipped the case on EVERY host while its spec refs still counted as coverage. Platform names are now validated against KNOWN_TARGETS at load time, like preview names. - Param-level preview overrides: traceability read preview flags from the BASE case only, while the runner honors per-param overrides — a param row adding `preview` was allowed to fail yet counted as coverage. The coverage computation now mirrors expand_case. Plus: the CLI harness compile step now runs under the per-case timeout (mirroring the spec runner — a comptime hang wedged the whole suite), and ice_message is a single shared pub implementation (the CLI copy deleted). Each hole verified by execution before AND after with scratch RUE_SPEC_DIR/RUE_SPEC_CASES corpora: dup-id gate 0->1, spaced-args uncovered ids now reported, only_on typo now a loud load failure, param-preview case now uncovered. The REAL corpus passes the strengthened gate (holes were latent, nothing was being masked). clippy clean, ./test.sh exit 0. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…all args), goto_join helper, dead API Five verified quality findings from the rue-cfg component review (2026-07-04; behavioral lanes — joins, divergence, opt soundness, drops — came back CLEAN): - --emit cfg now prints EnumVariant payload OPERANDS (enum_variant #3::0(v0, v1)) instead of an opaque count, matching StructInit/ArrayInit; a tuple variant's dataflow inputs were unreadable. - Codegen tracing (format_cfg_inst_data) now takes the Cfg: PlaceRead/ PlaceWrite render with projections RESOLVED ($0.#2.1) via a new pub Cfg::place_to_string instead of raw arena index ranges ($0[3..5]); and the Call/Intrinsic/StructInit arms print their actual args now that the "no Cfg access" constraint their comments cited is gone. - New CfgBuilder::goto_join single-sources the value-branch/join-param arity contract that was open-coded identically at THREE sites (if-then, if-else, match-arm) — the exact seam the RUE-347 bug class lives on; the copies could previously drift between constructs. - Deleted dead pub Place::is_simple (zero callers workspace-wide). Verified by execution: enum_variant dump shows operands; if/match join values correct at -O0 and -O2 (exit 42); enum payload round-trip exit 7. clippy clean, ./test.sh exit 0 (incl. golden corpora — the goto_join refactor is behavior-identical). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…n alloc failure, _start RSP contract Verified findings from the rue-runtime component review (2026-07-04) — the first sweep of this crate since the heap/StrBuf workstream landed. - MEMORY SAFETY (heap.rs): a near-usize::MAX @alloc wrapped the arena page round-up (unchecked align_up(total, 4096)) to zero, clamped up to the 64 KiB default arena, and handed out a NON-NULL pointer claiming a ~2^64-byte block backed by a 64 KiB mapping — a later write SIGSEGVs / corrupts memory. Now a checked round-up returns null, honoring the documented OOM contract. Reachable from a checked{} block via a 1-byte element type (bypasses RUE-345's upstream count*size multiply). Distinct from RUE-344 (alignment padding, already fixed). - MEMORY SAFETY (string.rs): __rue_String_push / push_str wrote through new_ptr.add(len) without checking new_ptr for null, so a failed string_ensure_capacity became a wild write through null+len. Both now republish the original string unchanged on allocation failure, matching the null-guard every sibling allocator already uses (all 3 arch copies). - heap.rs align > page size: blocks are placed at an offset aligned relative to the page-aligned arena base, so align > 4096 could return a misaligned address despite the doc guarantee. No reachable caller exceeds 8; over-page aligns are now rejected cleanly (null) rather than silently misaligned. - entry.rs x86-64 _start: the asm block did `sub rsp, 8` without restoring it and without options(noreturn), then fell through to platform::exit — an inline-asm stack-pointer contract violation (UB) that worked by luck. Added the matching `add rsp, 8`. The aarch64 entry points never had this. Verified by execution: the overflow @alloc now returns null (exit 0) instead of SIGSEGV; normal @alloc (42) and 4000-byte String growth (160) intact. New heap.rs unit tests pin the overflow and the over-page-align rejection; new CLI case pins the overflow end-to-end. clippy clean, ./test.sh exit 0. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…+ cmp->tst Cbz escape + sub dedup (aarch64) Latent-gap and drift fixes from the rue-codegen emit/peephole/schedule component review (2026-07-04). The behavioral core came back SOUND — the x86 peephole and scheduler were verified miscompile-free by -O0-vs-O3 differential + objdump byte-checks (no RUE-146 regression) — so these are defensive hardening, not live-bug fixes: - x86 emit_setcc / emit_movzx (byte operands): emitted REX only for r8-r15, so a byte r/m with encoding 4-7 (SPL/BPL/SIL/DIL) would decode as the legacy high-byte AH/CH/DH/BH — a silent wrong-register access. Now force a bare REX for encoding >= 4. Latent today (no reachable caller passes those regs); unit tests pin `sete sil` = 40 0F 94 C6 and `movzx eax, dil` = 40 0F B6 C7. Byte output for all current programs is unchanged. - aarch64 emit_cmp_imm: silently masked the immediate to 12 bits (a bare imm & 0xFFF) with no guard, unlike the RUE-45/129-hardened ADD/SUB encoders. Now uses the plain or LSL#12 12-bit form and panics on an unencodable immediate instead of truncating. Identical bytes for every current caller (all < 4096). - aarch64 cmp->tst peephole: Cbz/Cbnz (register-tested conditional branches) fell through the escape-boundary check, so the cmp flags could be treated dead across a taken conditional edge. Added them as escape boundaries — strictly conservative (can only suppress a rewrite), matching x86. - aarch64 emit_sub64_rr was byte-identical to emit_sub_rr; deleted the duplicate and pointed its one caller at emit_sub_rr (drift risk removal). Verified: x86 program correct at -O0 and -O2; aarch64 --emit asm unchanged for current programs (fixes are behavior-preserving by construction); codegen unit tests (incl. aarch64 byte-pinning) + ./test.sh green; clippy clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…et span Two verified findings from the rue-parser component review (2026-07-04): - ICE (robustness): the RUE-42 nesting-depth guard (check_nesting_depth) omitted the postfix `?` (try) operator from its counted-token set, so a long `x????...?` chain built AST depth unbounded by the 256 cap and overflowed the parser stack (exit 134, no diagnostic). `?` wraps in exactly one AST node like `.`, so it is now counted the same way; a long chain rejects cleanly with E0482. UI regression case added. - span: an index on the LHS of an assignment (AssignSuffix::Index) built its place span from index.span().end — stopping before the closing `]` — while the RHS index path already captures the bracket end. Threaded the `]` offset through AssignSuffix::Index so `a[i] = ...` and the intermediate `a[i]` in `a[i].f = ...` span the whole `base[index]`, matching the RHS. Verified by execution: 2000-`?` chain now E0482 (exit 1) not stack overflow; index assignment runs (a[1]=40 -> 41); parser unit tests + ./test.sh green; clippy clean. Larger findings (empty-body if/while/for misparse, exponential parse on unterminated braces / nested array literals, sub-parser duplication) filed separately. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Four small fixes from the rue-lexer/rue-span/rue-target review sweep: - Bare CR (U+000D) inside a string literal is now an unterminated-string error, matching bare LF, the backslash-before-EOL path, and spec 2.3:1 (CR is a line terminator) / 2.1:9. Previously a lone CR was silently swallowed as string content. - A leading UTF-8 BOM still errors (it is not in the spec whitespace set), but the E0001 diagnostic now carries a help explaining that the invisible character is a byte-order mark, instead of pointing a caret at nothing visible. - Spec 2.4:2/2.4:3 keyword tables now list pub, const, checked, unchecked, ptr, and Self — all reserved by the lexer and backing shipped features (modules, checked blocks, pointer types, Self), but absent from the normative tables. Spec tests pin each as rejected in identifier position. (Inverse direction of RUE-331, which stays open for impl/type.) - TokenKind doc said Ident/String carry a `Symbol`; the type is Spur. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…Es, purge dead surface
Fixes from the rue-error component review:
- UnexpectedCharacter/InvalidStringEscape messages render the offending
character through escape_debug, so a NUL/VT/BOM/control byte in source
shows as a visible \u{..} escape instead of corrupting the one-line
diagnostic with a raw control byte.
- The ice!/ice_error! macros now attach the compiler version
automatically (the doc claimed callers should - none ever did), so
graceful ICE reports carry the same version line as the panic-hook
banner. VERSION is single-sourced in rue-error; the CLI's --version
reads it through rue-compiler instead of a duplicate const.
- Deleted never-constructed surface: ErrorKind::UnexpectedEof/E0101
(the parser reports EOF via UnexpectedToken; number retired, not
reused), CompileError::at, and IceContext::with_target + its target
field (dead builder; per-ICE state belongs in details).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
…ifications From the rue-fuzz component review. Three lenses independently converged on the timeout hole; the CI hole was verified against two real nightly runs that found a crash nobody was told about. - harness: every input now runs under a wall-clock deadline (default 5s, --per-input-timeout). The pipe read and waitpid are both bounded; at the deadline the child is SIGKILLed and reported as a new RunOutcome::Timeout with its own dedup signature. Previously a hung compile (e.g. the RUE-374 superlinear parse) blocked waitpid forever: the entire hang/DoS bug class was undetectable AND one such input wedged the whole nightly run. - targets: sema/compiler targets panic on ErrorKind::InternalError in the returned errors — a graceful ice_error! is an ordinary Err, so it previously classified as a clean run and ICEs were invisible to fuzzing. - reproducers get a .meta sibling (target/signature/description) so CI artifacts are self-describing; mutation seed defaults to per-run entropy and is printed for replay (a fixed seed made every nightly re-explore an identical sequence). - fuzz.yml: add the missing issues:write permission — the notify step has been failing with 'Resource not accessible by integration' while real crashes were found (2026-07-03/04). Steps are continue-on-error with a failing aggregator so one crashing target no longer skips all later targets; new crashes comment on the open fuzz-crash issue instead of being silently suppressed; job/step timeout-minutes cap a wedged run. - purge: dead regalloc-oriented generators (arb_x86_mir_with_vregs and its private ladder) and the stale Regalloc-target claims; --help target list is now driven from all_targets(). - cache-probe.yml: pipefail so a failed measured build fails the step. The crash the broken notification ate is filed as RUE-381. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
The rubric (docs/process/spec-prose-rubric.md) converts the remaining chapter rewrites into checklist work: ID permanence, the procedure, register rules, category discipline, the banned folk-terms table, and escalation triggers, with the template PRs named. Template chapters, applying it: - 4.5 blocks: EBNF normative->syntax; 4.5:3 split (new 4.5:7 = no-final-expression case); 4.5:6 'destroyed' -> dropped newest-first, cited to 3.9 and core (D-EndScope); core citations added. - 4.6 if: 4.6:4 multi-claim split (new 4.6:10 = type of the if), never- coercion cross-ref added; 4.6:7 -> dynamic-semantics with (D-If-T)/ (D-If-F) citations; new 4.6:11 = no-else evaluates to (); new informative 4.6:12 pointing at the 3.8 branch-join rules. - 4.9 return: 'the implicit ()' retired (4.9:4 names the omitted-form rule); 'compatible with' -> identical up to the never coercion; 4.9:5 rationale split to informative 4.9:11; 4.9:7 now states the return-path drops (3.9:18, core (D-Return)). - 4.10 call: EBNF fixed to include inout/borrow call_arg modes (matching appendix A; new informative 4.10:10 for the place requirement, 6.1:17); 'compatible' -> identical up to never; new informative 4.10:11 cross-referencing argument-passing ownership; denotation rule 4.10:9 cited to (D-Call)/(D-Return-Value). Coverage: new gated IDs wired to existing cases (block_empty_is_unit -> 4.5:7; if type case -> 4.6:10; execute_then_branch -> 4.6:11). Traceability green (99.3%, only the 5 known-uncovered). Process docs' stale r[X.Y:Z#category] marker syntax corrected to the live shortcode. Part of RUE-202. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Owner
Author
|
Closing: this was created against the fork by mistake. Recreating against rue-language/rue. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes RUE-366 by making parameterized test expansion reject param keys that are neither known field overrides nor actually used as
{key}placeholders.The harness still supports:
name,source,error_contains,expected_error, stdout/stderr/stdin fieldsexit_code,compile_fail,preview,timeout_ms, andspec_extraerror_contains = "expected {ty}"This closes the false-green case where a typo like
exit_cod = 42insideparams = [{ ... }]was silently treated as an unused substitution variable.Validation
scripts/rue fmt./buck2 test root//crates/rue-test-runner:rue-test-runner-testscripts/rue testLinear: RUE-366