Skip to content

refactor(codegen): Layer 1 rooting migration slice 5 — the timer and namespace-call lowerings (#7615) - #7648

Merged
proggeramlug merged 5 commits into
mainfrom
layer1/slice5-lower-call
Aug 8, 2026
Merged

refactor(codegen): Layer 1 rooting migration slice 5 — the timer and namespace-call lowerings (#7615)#7648
proggeramlug merged 5 commits into
mainfrom
layer1/slice5-lower-call

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Slice 5 of the Layer 1 rooting migration (#7615). Two modules of the lower_call/ family migrated end to end, four live bugs found and fixed, and the reason the other four modules were left out recorded in the ledger because it is a statement about the API rather than about those files.

Migrated

module escape-hatch sites before ledger line
lower_call/extern_timers.rs 6 load-bearing (named lower_exprs_rooted / temp_root_release)
lower_call/namespace_call.rs 8 load-bearing (same)

Sabotage arm run per module — the assert stops at the first offender, so one run cannot speak for two. A compiling temp_root_push_double / temp_root_truncate pair was planted in each; the ledger test went red and named both lines by file:line; the build output was checked for error[ and could not compile, both 0, and Running unittests confirmed the test binary was actually reached. (A plant that fails to compile also makes the command non-zero, and a check grepping only for FAILED scores that build error as a successful sabotage.)

Live bugs

All four demonstrated against node 26.5.1 (the .node-version oracle) with a baseline compiler built from main in a separate target directory, and re-verified under PERRY_GC_ZEAL=1 PERRY_GC_PROTECT_FROMSPACE=1 PERRY_GC_PROTECT_FROMSPACE_DEPTH=800, compiled with PERRY_GC_MOVING_LOOP_POLLS=1.

1. Two-argument setTimeout / setInterval held the callback unrooted across the delay. #7210 rooted the trailing-argument forms and left the two-argument siblings alone, though the comment it wrote names the window exactly. The delay is an arbitrary expression:

%r6  = call i64 @js_closure_alloc(...)        ; the callback
%r8  = bitcast i64 %r7 to double              ; a BARE register
%r9  = call double @perry_fn_mod__churn()     ; the delay. User code, polls.
%r10 = call i64 @js_timer_validate_callback(double %r8, i32 0)   ; stale
node:      scheduled object / fired A
baseline:  TypeError [ERR_INVALID_ARG_TYPE]: The "callback" argument must be of
           type function. Received an instance of Object
fixed:     scheduled object / fired A

setInterval throws the same. A capturing callback is required — a non-capturing arrow lowers to js_closure_alloc_singleton, which is never moved, so the first reproducer I wrote passed while the IR clearly showed the window.

2. The var-shaped namespace export dispatched through a stale closure. ns.arrow(churn()) fetches the closure from its zero-arg getter first (spec order — the callee reference before the arguments), holds it in a bare register across every argument's lowering, then unbox_to_i64s it. #7280 taxonomy (a) and (c) at once: root_reload could not have repaired it, because the pointer is derived below the window from a register captured above it.

node:      a:1 / b:2
baseline:  TypeError: value is not a function
fixed:     a:1 / b:2

3. The has_rest namespace direct call lost every rest element — silently. The #7154 accumulator shape verbatim: current was a raw *mut ArrayHeader threaded through a push loop while the next argument's expression ran, holding the only reference to everything pushed so far. lower_rest_call_args_rooted was written for exactly this and this path never adopted it.

node:      head|r1,r2,r3
baseline:  head|
fixed:     head|r1,r2,r3

A wrong answer, not a crash. Delegating to the audited helper also pads fixed parameters to the declared arity, which the hand-rolled loop did not.

4. Three more unprotected windows, repaired in passing, real by inspection but without a reproducer that faults today: the fs/promises writeFile/appendFile/rmdir arms (path held across content and options), both V8-bridge arms (a bare for a in args { lower_expr }#7240's shape in a path that post-dates the fix), and the plain namespace direct-call argument loop.

False positives, counted in emissions

The one-argument setTimeout/setImmediate and all three clear* arms lower a single operand and consume it in the very next emission. No emission between production and use means no window, so they stay on bare lower_expr; routing them through with_operands_rooted would emit nothing anyway (any_may_trigger_gc over an empty tail is false) and would advertise a protection that is not there.

Cost

Zero where the window cannot collect, and now pinned rather than asserted. With a literal delay the emitted module contains no temp-root traffic at all and the callback register feeds js_timer_validate_callback directly. Across the whole 149-module gc_root_dominance_corpus.sh corpus, baseline and fixed IR are byte-identical (diff -rq → 0 differing files).

Tests

lower_call/timer_rooting_tests.rs, asserting on emitted IR rather than runtime behaviour. The runtime fault needs a capturing callback, a polling delay and the compile-time PERRY_GC_MOVING_LOOP_POLLS=1 (off by default since #7161) — a gap test would be green on the default build whether or not the fix is present, which is hazard 4.

The assertion is an ordering, not a slot count: a count across two programs that differ in an operand lets the delay's own rooting pay for the assertion. With an allocating delay the register js_timer_validate_callback reads must be defined below the delay's allocation; with a literal delay it must be the original register with zero temp-root traffic. Checked against the pre-fix source: the two ordering tests fail, the two zero-cost tests pass. Neither is vacuous. The liveness check excludes declare lines — the module always carries declare @js_timer_validate_callback whether or not anything calls it.

Not migrated, and why

Four of the six modules named on the tracker are left, and the reason is one sentence about this API: three of them need a re-read at more than one point, and every with_operands_rooted* form has exactly one.

  • lower_call/mod.rslower_call_args_rooted / lower_rest_call_args_rooted return a guard deliberately: their consumers in func_ref.rs are block-splitting specialized-ABI diamonds whose release must sit in a merge block post-dominating four dispatch paths, ~200 lines below the lowering. A closure form can express that only by swallowing the whole dispatch chain, in a file outside the slice.
  • lower_call/new.rsrefresh_rooted_args re-reads the same operand group at three caller-chosen points, under a temp_root_scope_begin/_end marker spanning ~20 return paths.
  • lower_call/console_promise.rslower_dynamic_closure_call re-reads receiver and callee below the arguments, then re-reads the arguments again below the allocating rebind unbox. Two stages, one combinator.
  • lower_call/early_branches.rs — its only escape-hatch uses are implicit_this_save/implicit_this_restore, already a paired combinator rather than the raw ordering API. Migrating it means re-exporting that pair through crate::rooting: a rename that would make the ledger line look substantive while asserting nothing new.

The concrete missing combinator is the variadic/rest shape — per-element re-reads between allocating pushes. namespace_call.rs's rest arm therefore delegates to super::lower_rest_call_args_rooted, which still names the raw API; that boundary is stated in the file header rather than hidden, and it is the same posture as calling lower_expr.

Two hazards in console_promise.rs were found while reading it and are not fixed here: console.table(a, b) and console.dir(a, b) both hold operand 0 in a bare register across operand 1's lowering. They are one-line fixes with the same combinator, but they belong with that module's migration rather than ahead of it.

Gates

The lint list was extracted from .github/workflows/test.yml, not recalled — 20 commands, all run individually:

ran=20 failed=0

Plus:

  • rustup run stable cargo fmt --all -- --check — clean
  • cargo test -p perry-codegen --lib --no-fail-fast — 706 passed, 0 failed
  • cargo test -p perry-runtime --lib --no-fail-fast — 1908 passed, 0 failed, 3 ignored
  • cargo check --all-targets — clean
  • gc_root_dominance_check.py --self-test — OK
  • gc_root_dominance_corpus.sh — 129/129 sources, 149 .ll
  • dominance mode (--moving-only --seeded-violations 40) — 40 planted, 40 caught, 0 MISSED, rc=0
  • --unrooted-allocas --moving-only0 violations, rc=0

Run without --moving-only both arms report the same 171 non-moving leads, all one fingerprint (js_object_alloc_class_inline_keysjs_gc_declare_typed_shape_layout). A/B'd against a baseline corpus: 171 on both, so they are pre-existing and untouched by this change; the gate uses --moving-only and sees 0 either way.

No version bump, no CHANGELOG.md edit; changelog fragment added under changelog.d/.

Summary by CodeRabbit

  • Bug Fixes

    • Improved reliability for timers, filesystem calls, namespace calls, V8 bridge operations, and variable-shaped exports during complex argument evaluation.
    • Preserved callback and argument values across operations that may trigger allocations.
    • Enforced the maximum supported argument count for closure exports.
    • Improved handling of rest-argument calls and filesystem arguments.
  • Tests

    • Added coverage for timer callback preservation and validation behavior.
    • Confirmed literal delays avoid unnecessary temporary handling.

Ralph Küpper added 3 commits August 8, 2026 15:54
…e Layer 1 rooting API (#7615)

Slice 5. Also fixes the unrooted 2-arg setTimeout/setInterval callback window and five unprotected windows in namespace_call.rs.
…rms (#7615)

Asserts an ordering rather than a slot count, so it cannot pass vacuously: verified red on the pre-fix source for the two hazard arms and green for the two zero-cost arms.
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Timer and namespace-call lowering now use with_operands_rooted, shared argument buffering, and rooted rest-call handling. LLVM IR tests verify callback re-reads across allocating delays and zero temporary-root traffic for literal delays. The rooting migration ledger, changelog, and package version were updated.

Changes

Lower-call rooting migration

Layer / File(s) Summary
Timer lowering and regression coverage
crates/perry-codegen/src/lower_call/extern_timers.rs, crates/perry-codegen/src/lower_call/timer_rooting_tests.rs, crates/perry-codegen/src/lower_call/mod.rs
Timer paths root callbacks, delays, and trailing arguments through with_operands_rooted and fill_arg_buffer. LLVM IR tests cover allocating and literal delays for setTimeout and setInterval.
Namespace and direct-call lowering
crates/perry-codegen/src/lower_call/namespace_call.rs
Namespace timer, filesystem, V8 bridge, closure-export, rest-function, and direct-call paths now lower operands inside rooted scopes. Missing arguments use undefined, and closure exports enforce the 16-argument limit.
Migration ledger and release documentation
crates/perry-codegen/src/rooting.rs, changelog.d/7648-layer1-slice5-lower-call.md, Cargo.toml, CLAUDE.md
The rooting ledger registers the migrated modules. The changelog records the migration and validation. The workspace and documented version advance to 0.5.1368.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • PerryTS/perry#7230: Both PRs modify timer and namespace-call lowering to root callbacks and staged arguments.
  • PerryTS/perry#7270: Both PRs add rooted argument lowering and use lower_rest_call_args_rooted.
  • PerryTS/perry#7252: Both PRs update lower_call argument lowering with rooted operands.

Suggested labels: bug

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the Layer 1 rooting migration and the two affected lowerings.
Description check ✅ Passed The description gives a detailed summary, concrete changes, related issue, test evidence, migration boundaries, and validation results.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch layer1/slice5-lower-call

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/perry-codegen/src/lower_call/namespace_call.rs (1)

62-65: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

undefined_lit duplicates the same helper in the parent module.

crates/perry-codegen/src/lower_call/mod.rs lines 63-65 defines an identical undefined_lit. Promote the parent one to pub(super)/pub(crate) and import it here instead of redefining it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-codegen/src/lower_call/namespace_call.rs` around lines 62 - 65,
Remove the duplicate undefined_lit helper from the namespace-call module, change
the parent module’s undefined_lit visibility to pub(super) or pub(crate), and
import and reuse that helper where needed in namespace_call.rs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/perry-codegen/src/lower_call/timer_rooting_tests.rs`:
- Around line 137-144: Update last_line_containing to exclude IR lines beginning
with or containing declare, matching the filtering behavior in
validate_call_line and the temp-root counter, so it returns only the relevant
call-site line for delay_alloc ordering assertions.

---

Nitpick comments:
In `@crates/perry-codegen/src/lower_call/namespace_call.rs`:
- Around line 62-65: Remove the duplicate undefined_lit helper from the
namespace-call module, change the parent module’s undefined_lit visibility to
pub(super) or pub(crate), and import and reuse that helper where needed in
namespace_call.rs.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b052a02e-6346-4a94-b2f6-f325cd9a9990

📥 Commits

Reviewing files that changed from the base of the PR and between 5bf576e and 1097960.

📒 Files selected for processing (6)
  • changelog.d/7648-layer1-slice5-lower-call.md
  • crates/perry-codegen/src/lower_call/extern_timers.rs
  • crates/perry-codegen/src/lower_call/mod.rs
  • crates/perry-codegen/src/lower_call/namespace_call.rs
  • crates/perry-codegen/src/lower_call/timer_rooting_tests.rs
  • crates/perry-codegen/src/rooting.rs

Comment on lines +137 to +144
fn last_line_containing(ir: &str, needle: &str) -> usize {
ir.lines()
.enumerate()
.filter(|(_, l)| l.contains(needle))
.map(|(i, _)| i)
.last()
.unwrap_or_else(|| panic!("no line containing {needle} in:\n{ir}"))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Exclude declare lines in last_line_containing, as the other two helpers do.

validate_call_line and the temp-root counter both drop declare lines. last_line_containing does not. The module carries declare i64 @js_object_alloc(...). If the emitter ever places declarations after function definitions, delay_alloc becomes the declaration index and the ordering assertion in assert_callback_survives_an_allocating_delay fails for a reason unrelated to rooting.

🛡️ Proposed fix to restrict the search to call sites
-/// Line index of the LAST occurrence of `needle`.
+/// Line index of the LAST occurrence of `needle`, ignoring `declare` lines —
+/// the module names the helper whether or not anything calls it.
 fn last_line_containing(ir: &str, needle: &str) -> usize {
     ir.lines()
         .enumerate()
+        .filter(|(_, l)| !l.trim_start().starts_with("declare"))
         .filter(|(_, l)| l.contains(needle))
         .map(|(i, _)| i)
         .last()
         .unwrap_or_else(|| panic!("no line containing {needle} in:\n{ir}"))
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn last_line_containing(ir: &str, needle: &str) -> usize {
ir.lines()
.enumerate()
.filter(|(_, l)| l.contains(needle))
.map(|(i, _)| i)
.last()
.unwrap_or_else(|| panic!("no line containing {needle} in:\n{ir}"))
}
fn last_line_containing(ir: &str, needle: &str) -> usize {
ir.lines()
.enumerate()
.filter(|(_, l)| !l.trim_start().starts_with("declare"))
.filter(|(_, l)| l.contains(needle))
.map(|(i, _)| i)
.last()
.unwrap_or_else(|| panic!("no line containing {needle} in:\n{ir}"))
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-codegen/src/lower_call/timer_rooting_tests.rs` around lines 137
- 144, Update last_line_containing to exclude IR lines beginning with or
containing declare, matching the filtering behavior in validate_call_line and
the temp-root counter, so it returns only the relevant call-site line for
delay_alloc ordering assertions.

Ralph Küpper added 2 commits August 8, 2026 16:18
Also records, in the slice 5 fragment, what review could and could not
reproduce: bug 3 reproduces on the plain default build with no GC knobs
(independently, with a different repro shape, printing nothing at exit 0),
while bugs 1 and 2 are arrangement-dependent at runtime and rest on the IR
window, which review confirmed directly in --trace llvm output.

Claude-Session: https://claude.ai/code/session_01Y1QZ5wUP9gRSwpiweT4Wix

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Cargo.toml`:
- Line 318: Synchronize the package metadata by updating Cargo.lock’s perry
package version to match the workspace version declared in Cargo.toml as
0.5.1368, preferably by regenerating the lockfile and preserving the manifest
bump.

In `@CLAUDE.md`:
- Line 11: Restore the release metadata to version 0.5.1367: update the Current
Version entry in CLAUDE.md at line 11 and the [workspace.package].version value
in Cargo.toml at line 318.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6a6b2608-fea9-456d-a833-71a5c3acf4ac

📥 Commits

Reviewing files that changed from the base of the PR and between 47c5744 and 2ad236a.

📒 Files selected for processing (3)
  • CLAUDE.md
  • Cargo.toml
  • changelog.d/7648-layer1-slice5-lower-call.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • changelog.d/7648-layer1-slice5-lower-call.md

Comment thread Cargo.toml

[workspace.package]
version = "0.5.1367"
version = "0.5.1368"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Synchronize Cargo.lock with the workspace version.

Cargo.lock still records perry as 0.5.1367, while this manifest declares 0.5.1368. If the version bump is retained, regenerate Cargo.lock; otherwise, restore the manifest version. The mismatch can break locked builds and package metadata consistency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Cargo.toml` at line 318, Synchronize the package metadata by updating
Cargo.lock’s perry package version to match the workspace version declared in
Cargo.toml as 0.5.1368, preferably by regenerating the lockfile and preserving
the manifest bump.

Comment thread CLAUDE.md
Perry is a native TypeScript compiler written in Rust that compiles TypeScript source code directly to native executables. It uses SWC for TypeScript parsing and LLVM for code generation.

**Current Version:** 0.5.1367
**Current Version:** 0.5.1368

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Keep release metadata in the maintainer-owned release flow.

External contributor PRs must not update the repository version fields.

  • CLAUDE.md#L11-L11: restore Current Version to 0.5.1367.
  • Cargo.toml#L318-L318: restore [workspace.package].version to 0.5.1367.
📍 Affects 2 files
  • CLAUDE.md#L11-L11 (this comment)
  • Cargo.toml#L318-L318
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CLAUDE.md` at line 11, Restore the release metadata to version 0.5.1367:
update the Current Version entry in CLAUDE.md at line 11 and the
[workspace.package].version value in Cargo.toml at line 318.

Sources: Coding guidelines, Learnings

@proggeramlug

Copy link
Copy Markdown
Contributor Author

Audit — merging as v0.5.1368

Bug 3 confirmed independently, and it is worse than the fragment said

I built a baseline from main myself in a separate target dir and wrote my own repro shape. The baseline prints completely empty stdout at exit 0 on the plain default build — no zeal, no protect, no PERRY_GC_MOVING_LOOP_POLLS. Node prints head|r1,r2,r3; your build prints it. The argument churn allocates enough to guarantee a collection inside the accumulator window, so unlike the other three this one needs no instrumentation to arrange. I've promoted it to lead the fragment and recorded the knob-free reproduction, because it is the only one of the four a user could hit today without trying.

Bugs 1 and 2: I could not reproduce the runtime fault — and you were right anyway

Your files, your commands, my main baseline: correct output. So I checked the thing that doesn't depend on what gets recycled, and the IR settles it:

%r7  = call i64 @js_closure_alloc_with_captures_singleton(...)   ; the callback
%r9  = bitcast i64 %r8 to double                                  ; BARE register
%r10 = call double @perry_fn_timer2b_ts__churn()                  ; user code, collects
%r11 = call i64 @js_timer_validate_callback(double %r9, i32 0)    ; reads the register defined above the call

and the fixed arm stores %r8 into a root slot and reloads it across the barrier. The window is there on baseline and closed by the fix — that is the durable evidence, and it is why the acceptance tests assert IR ordering rather than a runtime outcome.

Your correction #3 is the one I'd want future slices to inherit: a window that does not fault is not a window that is absent — it is one whose victim happened to survive. I've written that into the fragment next to the claim, along with the fact that review could not reproduce it. A changelog that says "the baseline throws X" where a reader following the instructions gets exit 0 costs more trust than the finding is worth; a changelog that says "here is the window, here is why the fault is arrangement-dependent" is checkable forever.

Your two corrections to my brief — both verified, both mine to own

--seeded-violations 40 is accepted and silently ignored in --unrooted-allocas mode: it prints no seeded line and no self-check runs. My brief's "both modes" was wrong; CI is consistent, since the workflow only passes it to the dominance step.

And cargo test … 2>&1 > log sends stderr to the terminal, so grep -c 'error\[' on that log counts stdout only and returns 0 no matter what. That is the sabotage-verification check I have been insisting on, being vacuous in exactly the way it exists to prevent — the same shape as the $?-after-a-pipe bug I hit twice in this session. Your fix (redirect order, plus asserting Running unittests appeared) is better than the check I asked for, because it proves the plant compiled and the binary was reached.

I did briefly suspect the --unrooted-allocas arm had no proof it can fire. Wrong — it has its own planted/control fixture pair (_SELFTEST_UNROOTED/_SELFTEST_ROOTED); I had read only the first of two self-test functions. Recording it so nobody re-derives the same false alarm.

Scope

2 of 6 modules is the right answer given what you found, and the reason is the valuable part: three of the four you left need a re-read at more than one point, and every with_operands_rooted* form has exactly one. That is a statement about the API, not about the files, and putting it in the ledger comment is where it belongs. early_branches.rs being a rename that would "make the ledger line look substantive while asserting nothing" is exactly the judgement this ledger needs to stay meaningful — a ledger padded with modules that never had a decision to make is worth less than a short honest one.

#7649 captures the missing variadic/rest combinator.

Gates: 20/20 from the lint job enumerated out of test.yml, cargo fmt clean, cargo check --all-targets clean, perry-codegen --lib 706 passed, perry-runtime --lib 1909 passed. Dominance corpus not re-run locally — you A/B'd it as byte-identical across all 149 modules, which is both the zero-cost proof and the reason the acceptance coverage had to be unit tests.

@proggeramlug
proggeramlug merged commit c8394bf into main Aug 8, 2026
12 checks passed
@proggeramlug
proggeramlug deleted the layer1/slice5-lower-call branch August 8, 2026 14:27
proggeramlug pushed a commit that referenced this pull request Aug 8, 2026
The version bumps in #7646/#7648 edited Cargo.toml and staged Cargo.lock
without a cargo invocation in between, so the lock kept 0.5.1367 and every
build dirtied the tree.

Claude-Session: https://claude.ai/code/session_01Y1QZ5wUP9gRSwpiweT4Wix
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant