test: pin apply*'s no-mutation property end to end - #707
Conversation
#620 point 5 reported that applying a variadic bytecode fn to a vector came back with the vector mutated -- rest packing appended into the caller's storage. #645 fixed it and pinned the invariant at the resolver, in TestResolveBytecodeCallVariadicPackingDoesNotMutateBorrowedArgs. Nothing covered the route into the resolver, and #620 Phase 3 rewrites that route. The guard is verified in both directions: against the pre-fix `append(prepared[:arity-1], restlist)` it fails with v = [1 (2 3) 3], and it passes on main. Public `apply` turns out never to have been exposed to this. For 2+ args it reaches apply* through list*/spread, which allocates a fresh seq, so the caller's storage is never the backing array -- only the raw apply* hands it straight through. Those assertions pass against the pre-fix code too, so the file marks them as characterization rather than letting them read as guards. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Ran the assertions against Placing it ahead of #620 Phase 3 is the right order too — it will fail loudly if the One factual correction, in the body and in the file's comment block. The claim that "for 2+ args (defn apply
([f args]
(let [s (seq args)]
(if s (apply* f s) (f))))
Nothing else blocking. Coverage is honestly scoped — only ArrayVector aliases, and a plain variadic |
The 2-arg arity of apply reaches apply* through (seq args), not list*/spread; it is safe because apply* only aliases an ArrayVector, and a seq view is not one. Cites both call sites so the claim is checkable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Confirmed, and fixed in both places. The mechanism I stated does not describe the arity the first characterization assertion exercises.
Rather than patch the 2-arg sentence, I replaced the explanation with the one that covers every arity: One deliberate change on the way through: the body names that branch by what it does instead of by line number. Your citation was accurate when you wrote it — #710 merged two and a half hours later and moved the Body updated; the comment block is fixed in |
Point 5 of my 2026-07-22 comment on #620 reported that applying a variadic bytecode fn to a vector handed the vector back mutated, because rest packing appended into the caller's storage:
#645 fixed that in
resolveBytecodeCalland pinned the invariant where the fix lives, inTestResolveBytecodeCallVariadicPackingDoesNotMutateBorrowedArgs. I said on #620 that no test pinned it, which was wrong — I searched forapply*in the test files and the guard does not mentionapply*anywhere. Correcting that separately on the issue.What is genuinely uncovered is the route into the resolver. The existing test calls
resolveBytecodeCalldirectly, so nothing asserts thatapply*reaches it, and #620 Phase 3 rewrites exactly that route while proposing a borrowed-argument rule. This adds the end-to-end spelling the original report used.The guard is verified in both directions
Against the pre-fix
append(prepared[:t.arity-1], restlist)it fails withv=[1 (2 3) 3]; onmainit passes. A test that passes either way would not be a ratchet.Public
applywas never exposed to thisapply*aliases the caller's storage only when it is handed anArrayVector— the one branch in itspkg/rt/lang.gobody that passesvs[1]toec.Invokeunchanged — and noapplyarity hands it one. The 2-arg arity passes(seq args), a seq view over the vector (applyinpkg/rt/core/core.lg); the 3+-arg arities pass alist*/spreadcons chain. Both reachapply*'s seq branch, which copies the arguments into a fresh slice viaappendSeqValues. Only the rawapply*hands the vector straight through.That means the
applyassertions pass against the pre-fix code as well. Rather than delete them or let them read as coverage they do not provide, the file groups them under a heading that says they are characterization, with the reason. If a later change makesapplyalias its argument, they start failing, which is the argument for writing them down instead of assuming.Thanks to @nnunley for catching this. The earlier wording attributed all of it to
list*/spread, which does not cover the 2-arg arity the first characterization assertion exercises. The test file's comment block carried the same claim and is fixed too.Placement
test/apply_rest_aliasing_test.lg, picked up byTestRunner's.lgwalk. If you would rather this rode the Phase 3 branch than land ahead of it, say so and I will close this — the offer on #620 was to keep it off that branch, not to insist.