refactor: simplification implement - #168
Conversation
Replace private cache queue and refcount assertions with public Pool execution, profiler, and native-code lifetime contracts. Queue ordering remains unobservable implementation state.
…eturn handling - Make function, closure, and coroutine heap slots immutable (Store rejects) - Add type guard in REF_SET/REF_GET for scalar-only cell access - Refactor retire into shared coroutine/normal return paths - Release discarded return values when coroutine completes - Preserve coroutine identity through tail calls - Add externalize-tests tool for test package refactoring - Update docs: memory model, host integration, instruction set
📝 WalkthroughWalkthroughThe PR converts Go tests to external ChangesExternal test migration
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (2 warnings, 1 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ast-grep (0.45.0)interp/threaded.goast-grep timed out on this file 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
internal/cmd/geninterp/lower.go (1)
3754-3812: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUse
types.BoxedNullwhen clearingco.value.
types.BoxedNullisBoxRef(0), buttypes.Boxed(0)is the literal boxed constant and has kind0/Default. SincereleaseBox(co.value)acts on ref values, this path can incorrectly release or later treat the reset value as non-null.🤖 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 `@internal/cmd/geninterp/lower.go` around lines 3754 - 3812, In the coroutine resume branch generated by resume, replace the reset assignment for co.value after releaseBox with types.BoxedNull instead of types.Boxed(0). Keep the existing releaseBox call and all other coroutine state handling unchanged.interp/pool_test.go (1)
245-278: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winAssert that the loop exit was compiled, not only that an attempt occurred.
runs = 64exceeds the currentexitThreshold, but that is hard-coded here while the assertion checks onlyvm_jit_attempts_total. Add one of the same positive indicators used in the nearby tests, such asvm_jit_emits_total> 0, so the test fails if the threshold changes and no side-exit compilation happens.🤖 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 `@interp/pool_test.go` around lines 245 - 278, Extend the assertions after the `metrics.Metric("vm_jit_attempts_total")` check in this pool test to verify a positive JIT emission indicator, such as `vm_jit_emits_total` being present and greater than zero. Keep the existing attempts assertion and use the same metric-validation pattern as nearby tests so the test confirms loop-exit compilation occurred.
🧹 Nitpick comments (11)
internal/cmd/geninterp/generate_test.go (1)
16-58: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPlace the scenarios under
t.Runsub-cases.
TestGeneratorruns five distinct scenarios in one linear body: build, first generation, clean-check, stale file, and missing file. A failure in an early scenario aborts the rest, and the failure output does not name the scenario. Wrap each scenario int.Runwhile keeping them sequential so the shared temporary directory state stays valid.Based on the coding guideline "Use one top-level test per public symbol (
Test<Func>orTest<Type>_<Method>), place sub-cases undert.Run".🤖 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 `@internal/cmd/geninterp/generate_test.go` around lines 16 - 58, Refactor TestGenerator so each scenario—build, initial generation, clean -check, stale-file check, and missing-file check—is enclosed in a descriptive sequential t.Run sub-case. Preserve the shared temp directory and generated-file state between sub-cases, and keep the existing assertions and command ordering intact.Source: Coding guidelines
benchmarks/control_test.go (2)
149-171: 📐 Maintainability & Code Quality | 🔵 TrivialVerify
program.Builder.MustBuild()and reduce duplicated build-and-panic boilerplate.
iterativeFibrepeatsprog, err := b.Build(); if err != nil { panic(err) }; return prog.call_test.goalready usestypes.NewFunctionBuilder(...).MustBuild()for function builders. Check whetherprogram.Builderexposes an equivalentMustBuild()method. This duplication repeats insievein this file, and inbenchmarks/memory_test.goandbenchmarks/numeric_test.go. See the consolidated comment for the full list of sites.🤖 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 `@benchmarks/control_test.go` around lines 149 - 171, Replace the manual Build error-handling sequence in iterativeFib with program.Builder’s MustBuild method if available, returning its result directly. Apply the same change to the corresponding sieve and benchmark builder sites identified in the consolidated review, while preserving each function’s existing behavior.
149-218: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract a shared program-builder panic helper.
program.Builderonly offersBuild(), while these benchmark helpers repeat the sameif err != nil { panic(err) } return progblock five times across three files. Encapsulate this pattern in a small package-level helper inbenchmarks/*; avoid usingMustBuild()ontypes.FunctionBuilderunless constructing that API forprogram.Builderas well.🤖 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 `@benchmarks/control_test.go` around lines 149 - 218, Extract a package-level helper for building a program.Builder that panics on Build errors and returns the resulting program. Replace the repeated build/error/return blocks in iterativeFib and sieve in benchmarks/control_test.go, and the corresponding blocks in benchmarks/memory_test.go and benchmarks/numeric_test.go, with this helper; do not use types.FunctionBuilder.MustBuild unless the same API is added for program.Builder.internal/cmd/geninterp/lower.go (2)
1238-1323: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDocument the intentional asymmetric
corohandling inreplace().In the
targetSlots == 1branch, the fresh-frame path setsf.coro = 0(Line 1257), but the frame-reuse path (Lines 1264-1278) never resetsf.coro, leaving it at whatever value the replaced frame held. The same pattern repeats in thetargetSlots != 1branch: fresh-frame setsf.coro = 0(Line 1300), reuse path (Lines 1307-1320) leaves it untouched. This is intentional:replace()implements tail calls (RETURN_CALL), and preservingf.corokeeps a tail-called function running on behalf of the same coroutine, matching the "preserves coroutine identity through function tail calls" test.The intent is not obvious from the generated code alone. A future edit that "fixes" the missing reset in the reuse path would silently break coroutine identity across tail calls. Add a
jen.Comment(...)at each reuse site to record why the reset is skipped.📝 Proposed comment additions
jen.Id("f").Dot("release").Op("=").Add(jen.Lit(releaseTarget)), + jen.Comment("coro is intentionally preserved across a tail call so a resumed coroutine keeps its identity."), jen.Id("i").Dot("sp").Op("=").Id("base").Op("+").Id("params").Op("+").Id("locals"), jen.Id(label).Op(":").Add(jen.Null()),Apply the equivalent comment before the second
i.sp = base + params + localsat Line 1320.🤖 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 `@internal/cmd/geninterp/lower.go` around lines 1238 - 1323, Add jen.Comment(...) immediately before each frame-reuse path’s `i.sp = base + params + locals` assignment in `replace`, covering both the `targetSlots == 1` and `targetSlots != 1` branches. Document that `f.coro` is intentionally not reset during tail-call frame reuse so coroutine identity is preserved; leave the existing fresh-frame resets unchanged.
3673-3736: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDeduplicate the repeated scalar-kind case list in
refGetandrefSet.Both functions embed the identical case list
types.I1, types.I8, types.I32, types.I64, types.F32, types.F64(Lines 3680-3684 and 3725-3729). Extract a shared helper that returns this case list so the two opcodes cannot drift out of sync if a new scalar kind is added later.♻️ Proposed extraction
func scalarKindCases() []jen.Code { return []jen.Code{ jen.Id("types").Dot("I1"), jen.Id("types").Dot("I8"), jen.Id("types").Dot("I32"), jen.Id("types").Dot("I64"), jen.Id("types").Dot("F32"), jen.Id("types").Dot("F64"), } }Then reference
scalarKindCases()...in place of the inlined list in bothrefGetandrefSet.🤖 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 `@internal/cmd/geninterp/lower.go` around lines 3673 - 3736, Extract the shared scalar-kind case list into a scalarKindCases helper returning the six existing jen.Code entries, then use its expanded result in the jen.Case calls within refGet and refSet. Preserve the current scalar kinds and default ErrTypeMismatch behavior while removing both duplicated inline lists.interp/coroutine_test.go (3)
26-26: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueName the top-level test after the public symbol under test.
The repository test convention requires
Test<Func>orTest<Type>_<Method>.TestCoroutineReferencesdoes not map to an exported symbol of packageinterp. The behaviors here are observed throughInterpreter.Run, soTestInterpreter_Runwith these sub-cases matches the convention. If a separate grouping is preferred, keep the sub-cases and rename the top-level function to a public symbol name.As per coding guidelines: "Use one top-level test per public symbol (
Test<Func>orTest<Type>_<Method>), place sub-cases undert.Run".🤖 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 `@interp/coroutine_test.go` at line 26, Rename the top-level test function TestCoroutineReferences to TestInterpreter_Run so it follows the repository convention and reflects the public symbol under test. Keep the existing coroutine scenarios as sub-cases under t.Run.Source: Coding guidelines
69-73: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueExplain the
19instruction-pointer literal.
19is the encoded byte offset of theRESUMEinstruction in this module. A change to instruction widths moves that offset.require.True(t, collected)keeps the failure visible, so correctness is preserved. Add a short comment that states which instruction the offset targets, so a future edit can be updated quickly.🤖 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 `@interp/coroutine_test.go` around lines 69 - 73, Add a brief comment next to the IP comparison in the interp.WithHook callback explaining that 19 is the encoded byte offset of the RESUME instruction, making clear the literal must be updated if instruction widths change.
220-228: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winState how the heap cap proves the release.
require.Equal(t, 3, vm.Len())checks the stack only. The real proof isrequire.NoError(t, vm.Run(...))withWithHeapLimit(5): if the discarded completion reference leaked, the lastREF_NEWwould returnErrHeapExhausted. Add a one-line comment that records this dependency, so a future heap-size change does not silently remove the coverage.🤖 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 `@interp/coroutine_test.go` around lines 220 - 228, The test currently relies on the heap limit to detect leaked completion references, but this dependency is undocumented. Add a one-line comment near the vm.Run assertion explaining that WithHeapLimit(5) makes a leaked discarded reference cause the final REF_NEW to return ErrHeapExhausted, preserving the release coverage.interp/pool_test.go (2)
584-643: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMove the
seconddeclaration into the loop.
secondis declared at line 584 but assigned and read only inside the iteration body. Declaring it inside the loop keeps the measured value local and removes the cross-iteration carry-over. The timer handling is correct:b.StopTimer()before the loop andb.StartTimer()only aroundpool.Getmeasures the shared-JIT miss path.🤖 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 `@interp/pool_test.go` around lines 584 - 643, Move the var second *interp.Interpreter declaration into the benchmark iteration loop, immediately before its first assignment from pool.Get. Keep all existing uses, timer handling, and pool lifecycle logic unchanged while eliminating cross-iteration state.
120-120: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRecord that
0xffffffffencodes-1.The removed
i32operandhelper made the sign explicit.instr.Newtakes an unsigned operand, so the literal is correct. Add a named constant or a short comment, so the negative branch value stays readable.♻️ Proposed change
Bind(neg). - Emit(instr.New(instr.I32_CONST, 0xffffffff)). + // 0xffffffff is the two's-complement encoding of i32 -1. + Emit(instr.New(instr.I32_CONST, 0xffffffff)).🤖 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 `@interp/pool_test.go` at line 120, Document the `0xffffffff` operand in the test around `instr.New` as the encoded `-1` branch value, using a named constant or concise comment. Keep the unsigned literal and existing instruction behavior unchanged.interp/host_test.go (1)
490-499: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueClose the interpreter with
deferinside the loop body.Line 498 closes the interpreter as the last statement. If any earlier
requirein the same iteration fails, the test goroutine stops and the interpreter is never closed. The same pattern exists at lines 254-264 and 290-295. Wrap each iteration in a closure withdefer i.Close(), or registert.Cleanup. This keeps failures from leaking interpreter resources into the rest of the test binary.🤖 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 `@interp/host_test.go` around lines 490 - 499, Update the test loop iterations that create interpreters, including the shown host-object case and the analogous sections near the other referenced cases, to register interpreter cleanup immediately after creation using a closure-scoped defer or t.Cleanup. Remove the explicit final i.Close calls where cleanup is registered, while preserving the existing assertions and error handling.
🤖 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 `@cli/cli_test.go`:
- Line 18: Update TestWithFS to inject a distinct cli.WriteFS implementation
instead of cli.OS(), ensuring it can detect whether cli.WithFS is honored.
Configure the test filesystem with main.vm and verify the rendered result is
read from that injected filesystem through cli.Root.
In `@docs/instruction-set.md`:
- Around line 299-300: Update docs/instruction-set.md lines 299-300 to state
that REF_GET, like REF_SET, traps when targeting a non-scalar reference. In
interp/interp_test.go lines 287-294, add a callable-reference REF_GET case
asserting ErrTypeMismatch; no other sites require changes.
In `@externalize_tests_tmp.go`:
- Around line 102-112: Update the source rewrite flow around os.Create and
format.Node to format each file into memory, write the complete result to an
adjacent temporary file, close it with error handling, and rename it over the
target only after all operations succeed. Ensure failures leave the original
compiled Go source intact and remove the direct truncating write path.
In `@interp/interp_test.go`:
- Around line 24-37: Move the public-contract tests from interp/interp_test.go
ranges 24-37, 1552-1585, and 4328-4341 into an external package interp_test
file. Relocate optionCustom and qualify its Interpreter, types, and errors
through interp as needed; move the scalar REF_* contract cases and
converter-precedence case unchanged in behavior, ensuring all three sites test
the importing-client boundary.
- Around line 1575-1576: Update the test cleanup around the interpreter created
with New and WithThreshold(-1) to check the error returned by Close using
require.NoError, ensuring cleanup failures are reported instead of discarded.
In `@types/function_test.go`:
- Around line 12-41: Replace representation-based assertions with checks of
exported, observable behavior across types/function_test.go ranges 12-41, 79-84,
115-125, and 153-165; avoid direct FunctionType, Function, Handlers, Locals,
Code, and slot-storage access, adding public accessors only for intentional API
contracts. Apply the same change in types/array_test.go:11-18 and
types/closure_test.go:50-60 by testing array and closure behavior instead of
Typ, Elems, Fn, or Upvals fields. Update optimize/optimizer_test.go:175-180,
278-283, and 338-344 to validate optimization results through public behavior
rather than constants, local counts, bytecode layout, or handler storage.
In `@types/map_test.go`:
- Around line 11-79: Remove representation-level assertions from
types/map_test.go lines 11-79 by testing public map behavior and type methods
instead of Typ, Zero, and map-type fields; update types/struct_test.go lines
15-34 to use Struct.Type and Field behavior rather than Struct.Typ/Data; update
types/struct_test.go lines 126-130 to verify public field lookup rather than
StructType.Fields; update program/parse_test.go lines 15-88 to compare
serialized output or use public accessors instead of direct program section
fields.
---
Outside diff comments:
In `@internal/cmd/geninterp/lower.go`:
- Around line 3754-3812: In the coroutine resume branch generated by resume,
replace the reset assignment for co.value after releaseBox with types.BoxedNull
instead of types.Boxed(0). Keep the existing releaseBox call and all other
coroutine state handling unchanged.
In `@interp/pool_test.go`:
- Around line 245-278: Extend the assertions after the
`metrics.Metric("vm_jit_attempts_total")` check in this pool test to verify a
positive JIT emission indicator, such as `vm_jit_emits_total` being present and
greater than zero. Keep the existing attempts assertion and use the same
metric-validation pattern as nearby tests so the test confirms loop-exit
compilation occurred.
---
Nitpick comments:
In `@benchmarks/control_test.go`:
- Around line 149-171: Replace the manual Build error-handling sequence in
iterativeFib with program.Builder’s MustBuild method if available, returning its
result directly. Apply the same change to the corresponding sieve and benchmark
builder sites identified in the consolidated review, while preserving each
function’s existing behavior.
- Around line 149-218: Extract a package-level helper for building a
program.Builder that panics on Build errors and returns the resulting program.
Replace the repeated build/error/return blocks in iterativeFib and sieve in
benchmarks/control_test.go, and the corresponding blocks in
benchmarks/memory_test.go and benchmarks/numeric_test.go, with this helper; do
not use types.FunctionBuilder.MustBuild unless the same API is added for
program.Builder.
In `@internal/cmd/geninterp/generate_test.go`:
- Around line 16-58: Refactor TestGenerator so each scenario—build, initial
generation, clean -check, stale-file check, and missing-file check—is enclosed
in a descriptive sequential t.Run sub-case. Preserve the shared temp directory
and generated-file state between sub-cases, and keep the existing assertions and
command ordering intact.
In `@internal/cmd/geninterp/lower.go`:
- Around line 1238-1323: Add jen.Comment(...) immediately before each
frame-reuse path’s `i.sp = base + params + locals` assignment in `replace`,
covering both the `targetSlots == 1` and `targetSlots != 1` branches. Document
that `f.coro` is intentionally not reset during tail-call frame reuse so
coroutine identity is preserved; leave the existing fresh-frame resets
unchanged.
- Around line 3673-3736: Extract the shared scalar-kind case list into a
scalarKindCases helper returning the six existing jen.Code entries, then use its
expanded result in the jen.Case calls within refGet and refSet. Preserve the
current scalar kinds and default ErrTypeMismatch behavior while removing both
duplicated inline lists.
In `@interp/coroutine_test.go`:
- Line 26: Rename the top-level test function TestCoroutineReferences to
TestInterpreter_Run so it follows the repository convention and reflects the
public symbol under test. Keep the existing coroutine scenarios as sub-cases
under t.Run.
- Around line 69-73: Add a brief comment next to the IP comparison in the
interp.WithHook callback explaining that 19 is the encoded byte offset of the
RESUME instruction, making clear the literal must be updated if instruction
widths change.
- Around line 220-228: The test currently relies on the heap limit to detect
leaked completion references, but this dependency is undocumented. Add a
one-line comment near the vm.Run assertion explaining that WithHeapLimit(5)
makes a leaked discarded reference cause the final REF_NEW to return
ErrHeapExhausted, preserving the release coverage.
In `@interp/host_test.go`:
- Around line 490-499: Update the test loop iterations that create interpreters,
including the shown host-object case and the analogous sections near the other
referenced cases, to register interpreter cleanup immediately after creation
using a closure-scoped defer or t.Cleanup. Remove the explicit final i.Close
calls where cleanup is registered, while preserving the existing assertions and
error handling.
In `@interp/pool_test.go`:
- Around line 584-643: Move the var second *interp.Interpreter declaration into
the benchmark iteration loop, immediately before its first assignment from
pool.Get. Keep all existing uses, timer handling, and pool lifecycle logic
unchanged while eliminating cross-iteration state.
- Line 120: Document the `0xffffffff` operand in the test around `instr.New` as
the encoded `-1` branch value, using a named constant or concise comment. Keep
the unsigned literal and existing instruction behavior unchanged.
🪄 Autofix (Beta)
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: 7f94739c-6cce-40b1-aaa9-98da0dee6ff8
📒 Files selected for processing (85)
.claude/CLAUDE.mdAGENTS.mdanalysis/blocks_test.goanalysis/gvn_test.goasm/amd64/encoder_test.goasm/arm64/encoder_test.goasm/arm64/frame_test.goasm/arm64/instr_test.goasm/arm64/relax_test.goasm/buffer_test.goasm/instr_test.goasm/operand_test.goasm/reg_test.gobenchmarks/benchmark_test.gobenchmarks/call.gobenchmarks/call_test.gobenchmarks/compare_test.gobenchmarks/control.gobenchmarks/control_test.gobenchmarks/memory.gobenchmarks/memory_test.gobenchmarks/numeric.gobenchmarks/numeric_test.gocli/cli_test.gocli/fs_test.gocli/repl_test.gocli/run_test.godebug/debugger_test.godocs/coding-patterns.mddocs/host-integration.mddocs/instruction-set.mddocs/memory-model.mddocs/testing.mdexternalize_tests_tmp.goinstr/builder_test.goinstr/code_test.goinstr/fuzz_test.goinstr/instr_test.goinstr/kind_test.goinstr/opcode_test.goinstr/parse_test.goinstr/type_test.gointernal/cmd/geninterp/generate_test.gointernal/cmd/geninterp/lower.gointerp/cache_test.gointerp/coroutine.gointerp/coroutine_test.gointerp/error_test.gointerp/host_test.gointerp/interp.gointerp/interp_test.gointerp/marshal_test.gointerp/pool_test.gointerp/threaded.gooptimize/fuzz_test.gooptimize/optimizer_test.gopass/manager_test.gopass/pipeline_test.goprof/collector_test.goprof/jit_test.goprof/profiler_test.goprogram/builder_test.goprogram/fuzz_test.goprogram/parse_test.goprogram/program_test.goprogram/verify_test.gotransform/as_test.gotransform/cd_test.gotransform/cf_test.gotransform/dce_test.gotransform/gvn_test.gotransform/rewrite_test.gotypes/array_test.gotypes/boxed_test.gotypes/closure_test.gotypes/error_test.gotypes/function_test.gotypes/fuzz_test.gotypes/iterator_test.gotypes/map_test.gotypes/parse_test.gotypes/primitive_test.gotypes/string_test.gotypes/struct_test.gotypes/value_test.go
💤 Files with no reviewable changes (6)
- benchmarks/numeric.go
- benchmarks/control.go
- benchmarks/call.go
- transform/rewrite_test.go
- benchmarks/memory.go
- interp/cache_test.go
👮 Files not reviewed due to content moderation or server errors (1)
- interp/threaded.go
|
|
||
| out := bytes.NewBuffer(nil) | ||
| root := Root(WithFS(OS())) | ||
| root := cli.Root(cli.WithFS(cli.OS())) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Use a distinct filesystem in TestWithFS.
cli.Root already uses cli.OS() by default. Passing cli.WithFS(cli.OS()) has no observable effect. This test cannot detect an ignored WithFS option. Inject a distinct cli.WriteFS implementation and read main.vm from that implementation.
As per coding guidelines, tests must verify public observable behavior.
🤖 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 `@cli/cli_test.go` at line 18, Update TestWithFS to inject a distinct
cli.WriteFS implementation instead of cli.OS(), ensuring it can detect whether
cli.WithFS is honored. Configure the test filesystem with main.vm and verify the
rendered result is read from that injected filesystem through cli.Root.
Source: Coding guidelines
| `REF_SET` mutates scalar cells created by `REF_NEW`; non-scalar targets trap. | ||
| Coroutine tail calls preserve the current coroutine. On completion, `CORO_VALUE` exposes the last declared return; earlier returns are discarded. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Document and test non-scalar REF_GET traps.
The PR objective requires scalar-only checks for both REF_SET and REF_GET. The documentation and added rejection test cover only REF_SET.
docs/instruction-set.md#L299-L300: State thatREF_GETalso traps for non-scalar targets.interp/interp_test.go#L287-L294: Add a callable-referenceREF_GETcase that expectsErrTypeMismatch.
As per coding guidelines, “Update the owning documentation when behavior [or] invariants ... change.”
📍 Affects 2 files
docs/instruction-set.md#L299-L300(this comment)interp/interp_test.go#L287-L294
🤖 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 `@docs/instruction-set.md` around lines 299 - 300, Update
docs/instruction-set.md lines 299-300 to state that REF_GET, like REF_SET, traps
when targeting a non-scalar reference. In interp/interp_test.go lines 287-294,
add a callable-reference REF_GET case asserting ErrTypeMismatch; no other sites
require changes.
Source: Coding guidelines
| output, err := os.Create(pkg.CompiledGoFiles[index]) | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
| if err := format.Node(output, pkg.Fset, file); err != nil { | ||
| output.Close() | ||
| panic(err) | ||
| } | ||
| if err := output.Close(); err != nil { | ||
| panic(err) | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Prevent partial source-file rewrites.
os.Create truncates the target before formatting and writing complete. If format.Node, a write, or Close fails, the tool can leave a partial Go source file. Format into memory, write an adjacent temporary file, close it, and rename it only after success. This also removes the unchecked Close error on Line 107.
🧰 Tools
🪛 golangci-lint (2.12.2)
[error] 107-107: Error return value of output.Close is not checked
(errcheck)
🤖 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 `@externalize_tests_tmp.go` around lines 102 - 112, Update the source rewrite
flow around os.Create and format.Node to format each file into memory, write the
complete result to an adjacent temporary file, close it with error handling, and
rename it over the target only after all operations succeed. Ensure failures
leave the original compiled Go source intact and remove the direct truncating
write path.
Source: Linters/SAST tools
| type optionCustom int32 | ||
|
|
||
| func (v optionCustom) MarshalVM(*Interpreter) (types.Value, error) { | ||
| return types.I32(v), nil | ||
| } | ||
|
|
||
| func (v *optionCustom) UnmarshalVM(_ *Interpreter, value types.Value) error { | ||
| n, ok := value.(types.I32) | ||
| if !ok { | ||
| return ErrTypeMismatch | ||
| } | ||
| *v = optionCustom(n) | ||
| return nil | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift
Move these public-contract tests to package interp_test.
These additions use the internal interp package. They do not validate the importing-client boundary required by this PR.
interp/interp_test.go#L24-L37: MoveoptionCustomto an external test file and qualify interpreter types throughinterp.interp/interp_test.go#L1552-L1585: Move the scalarREF_*contract cases to an external test file.interp/interp_test.go#L4328-L4341: Move the converter-precedence case to an external test file.
As per coding guidelines, “Every test package must use the production package name plus _test and act as an importing client.”
📍 Affects 1 file
interp/interp_test.go#L24-L37(this comment)interp/interp_test.go#L1552-L1585interp/interp_test.go#L4328-L4341
🤖 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 `@interp/interp_test.go` around lines 24 - 37, Move the public-contract tests
from interp/interp_test.go ranges 24-37, 1552-1585, and 4328-4341 into an
external package interp_test file. Relocate optionCustom and qualify its
Interpreter, types, and errors through interp as needed; move the scalar REF_*
contract cases and converter-precedence case unchanged in behavior, ensuring all
three sites test the importing-client boundary.
Source: Coding guidelines
| i := New(prog, WithThreshold(-1)) | ||
| defer i.Close() |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Check the Close error.
Line 1576 discards a cleanup failure. Register cleanup with require.NoError so the test reports leaked-reference or shutdown failures.
Proposed fix
i := New(prog, WithThreshold(-1))
- defer i.Close()
+ t.Cleanup(func() { require.NoError(t, i.Close()) })📝 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.
| i := New(prog, WithThreshold(-1)) | |
| defer i.Close() | |
| i := New(prog, WithThreshold(-1)) | |
| t.Cleanup(func() { require.NoError(t, i.Close()) }) |
🧰 Tools
🪛 golangci-lint (2.12.2)
[error] 1576-1576: Error return value of i.Close is not checked
(errcheck)
🤖 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 `@interp/interp_test.go` around lines 1575 - 1576, Update the test cleanup
around the interpreter created with New and WithThreshold(-1) to check the error
returned by Close using require.NoError, ensuring cleanup failures are reported
instead of discarded.
Source: Linters/SAST tools
| func TestNewFunctionBuilder(t *testing.T) { | ||
| b := NewFunctionBuilder(nil) | ||
| b := types.NewFunctionBuilder(nil) | ||
| fn, err := b.Build() | ||
| require.NoError(t, err) | ||
| require.Equal(t, &FunctionType{}, fn.Typ) | ||
| require.Equal(t, &types.FunctionType{}, fn.Typ) | ||
| } | ||
|
|
||
| func TestFunctionBuilder_Params(t *testing.T) { | ||
| fn := NewFunctionBuilder(nil).Params(TypeI32, TypeRef).MustBuild() | ||
| require.Equal(t, []Type{TypeI32, TypeRef}, fn.Typ.Params) | ||
| fn := types.NewFunctionBuilder(nil).Params(types.TypeI32, types.TypeRef).MustBuild() | ||
| require.Equal(t, []types.Type{types.TypeI32, types.TypeRef}, fn.Typ.Params) | ||
| } | ||
|
|
||
| func TestFunctionBuilder_Returns(t *testing.T) { | ||
| fn := NewFunctionBuilder(nil).Returns(TypeI32, TypeRef).MustBuild() | ||
| require.Equal(t, []Type{TypeI32, TypeRef}, fn.Typ.Returns) | ||
| fn := types.NewFunctionBuilder(nil).Returns(types.TypeI32, types.TypeRef).MustBuild() | ||
| require.Equal(t, []types.Type{types.TypeI32, types.TypeRef}, fn.Typ.Returns) | ||
| } | ||
|
|
||
| func TestFunctionBuilder_Locals(t *testing.T) { | ||
| fn := NewFunctionBuilder(nil).Locals(TypeI32, TypeRef).MustBuild() | ||
| require.Equal(t, []Type{TypeI32, TypeRef}, fn.Locals) | ||
| fn := types.NewFunctionBuilder(nil).Locals(types.TypeI32, types.TypeRef).MustBuild() | ||
| require.Equal(t, []types.Type{types.TypeI32, types.TypeRef}, fn.Locals) | ||
| } | ||
|
|
||
| func TestFunctionBuilder_Captures(t *testing.T) { | ||
| fn := NewFunctionBuilder(nil).Captures(TypeI32, TypeF64).MustBuild() | ||
| require.Equal(t, []Type{TypeI32, TypeF64}, fn.Captures) | ||
| fn := types.NewFunctionBuilder(nil).Captures(types.TypeI32, types.TypeF64).MustBuild() | ||
| require.Equal(t, []types.Type{types.TypeI32, types.TypeF64}, fn.Captures) | ||
| } | ||
|
|
||
| func TestFunctionBuilder_Emit(t *testing.T) { | ||
| fn := NewFunctionBuilder(nil).Emit(instr.New(instr.I32_CONST, 42), instr.New(instr.RETURN)).MustBuild() | ||
| fn := types.NewFunctionBuilder(nil).Emit(instr.New(instr.I32_CONST, 42), instr.New(instr.RETURN)).MustBuild() | ||
| require.Equal(t, []instr.Instruction{instr.New(instr.I32_CONST, 42), instr.New(instr.RETURN)}, instr.Unmarshal(fn.Code)) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift
Replace representation assertions with public behavior assertions.
The external-test migration still reads implementation representations. These assertions couple client tests to storage layout and encoded implementation details. Test exported behavior instead. Add a public accessor only if that detail is an intended supported contract.
types/function_test.go#L12-L41: Replace directFunctionandFunctionTypefield checks with exported behavior checks.types/function_test.go#L79-L84: Do not assertFunction.Handlersdirectly.types/function_test.go#L115-L125: Do not assertFunction.Typ,Function.Locals, orFunction.Codedirectly.types/function_test.go#L153-L165: Do not inspect function fields to test slot construction.types/array_test.go#L11-L18: Do not assertArray.TyporArray.Elemsdirectly.types/closure_test.go#L50-L60: Do not assertClosure.Typ,Closure.Fn, orClosure.Upvalsdirectly.optimize/optimizer_test.go#L175-L180: Do not inspect optimized constants, locals, or bytecode layout.optimize/optimizer_test.go#L278-L283: Do not inspect optimized constants or local counts.optimize/optimizer_test.go#L338-L344: Do not inspect optimized constants or handler storage.
As per coding guidelines, tests “must not access private symbols or representations and must assert public observable behavior.”
📍 Affects 4 files
types/function_test.go#L12-L41(this comment)types/function_test.go#L79-L84types/function_test.go#L115-L125types/function_test.go#L153-L165types/array_test.go#L11-L18types/closure_test.go#L50-L60optimize/optimizer_test.go#L175-L180optimize/optimizer_test.go#L278-L283optimize/optimizer_test.go#L338-L344
🤖 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 `@types/function_test.go` around lines 12 - 41, Replace representation-based
assertions with checks of exported, observable behavior across
types/function_test.go ranges 12-41, 79-84, 115-125, and 153-165; avoid direct
FunctionType, Function, Handlers, Locals, Code, and slot-storage access, adding
public accessors only for intentional API contracts. Apply the same change in
types/array_test.go:11-18 and types/closure_test.go:50-60 by testing array and
closure behavior instead of Typ, Elems, Fn, or Upvals fields. Update
optimize/optimizer_test.go:175-180, 278-283, and 338-344 to validate
optimization results through public behavior rather than constants, local
counts, bytecode layout, or handler storage.
Source: Coding guidelines
| func TestNewTypedMap(t *testing.T) { | ||
| typ := NewMapType(TypeI32, TypeRef) | ||
| m := NewTypedMap[int32](typ, 4) | ||
| typ := types.NewMapType(types.TypeI32, types.TypeRef) | ||
| m := types.NewTypedMap[int32](typ, 4) | ||
| require.Same(t, typ, m.Typ) | ||
| require.Equal(t, BoxedNull, m.Zero) | ||
| require.Equal(t, types.BoxedNull, m.Zero) | ||
| require.Zero(t, m.Len()) | ||
| } | ||
|
|
||
| func TestNewMap(t *testing.T) { | ||
| typ := NewMapType(TypeRef, TypeI32) | ||
| m := NewMap(typ) | ||
| typ := types.NewMapType(types.TypeRef, types.TypeI32) | ||
| m := types.NewMap(typ) | ||
| require.Same(t, typ, m.Typ) | ||
| require.Equal(t, BoxI32(0), m.Zero) | ||
| require.Equal(t, types.BoxI32(0), m.Zero) | ||
| require.Zero(t, m.Len()) | ||
| } | ||
|
|
||
| func TestNewMapWithCapacity(t *testing.T) { | ||
| typ := NewMapType(TypeI32, TypeRef) | ||
| m := NewMapWithCapacity(typ, 8) | ||
| typ := types.NewMapType(types.TypeI32, types.TypeRef) | ||
| m := types.NewMapWithCapacity(typ, 8) | ||
| require.Same(t, typ, m.Typ) | ||
| require.Equal(t, BoxedNull, m.Zero) | ||
| require.Equal(t, types.BoxedNull, m.Zero) | ||
| require.Zero(t, m.Len()) | ||
| } | ||
|
|
||
| func TestNewMapForType(t *testing.T) { | ||
| structType := NewStructType(NewStructField(TypeI32)) | ||
| structType := types.NewStructType(types.NewStructField(types.TypeI32)) | ||
| tests := []struct { | ||
| typ *MapType | ||
| typ *types.MapType | ||
| want any | ||
| }{ | ||
| {typ: NewMapType(TypeI32, TypeI32), want: (*TypedMap[int32])(nil)}, | ||
| {typ: NewMapType(TypeI64, TypeI32), want: (*TypedMap[int64])(nil)}, | ||
| {typ: NewMapType(TypeF32, TypeI32), want: (*TypedMap[float32])(nil)}, | ||
| {typ: NewMapType(TypeF64, TypeI32), want: (*TypedMap[float64])(nil)}, | ||
| {typ: NewMapType(TypeRef, TypeI32), want: (*Map)(nil)}, | ||
| {typ: NewMapType(TypeString, TypeI32), want: (*Map)(nil)}, | ||
| {typ: NewMapType(structType, TypeI32), want: (*Map)(nil)}, | ||
| {typ: types.NewMapType(types.TypeI32, types.TypeI32), want: (*types.TypedMap[int32])(nil)}, | ||
| {typ: types.NewMapType(types.TypeI64, types.TypeI32), want: (*types.TypedMap[int64])(nil)}, | ||
| {typ: types.NewMapType(types.TypeF32, types.TypeI32), want: (*types.TypedMap[float32])(nil)}, | ||
| {typ: types.NewMapType(types.TypeF64, types.TypeI32), want: (*types.TypedMap[float64])(nil)}, | ||
| {typ: types.NewMapType(types.TypeRef, types.TypeI32), want: (*types.Map)(nil)}, | ||
| {typ: types.NewMapType(types.TypeString, types.TypeI32), want: (*types.Map)(nil)}, | ||
| {typ: types.NewMapType(structType, types.TypeI32), want: (*types.Map)(nil)}, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.typ.Key.String(), func(t *testing.T) { | ||
| require.IsType(t, tt.want, NewMapForType(tt.typ, 0)) | ||
| require.IsType(t, tt.want, types.NewMapForType(tt.typ, 0)) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestNewMapIterator(t *testing.T) { | ||
| m := NewTypedMap[int64](NewMapType(TypeI64, TypeI32), 0) | ||
| it := NewMapIterator(7, m) | ||
| require.Equal(t, NewIteratorType(TypeI64), it.Type()) | ||
| m := types.NewTypedMap[int64](types.NewMapType(types.TypeI64, types.TypeI32), 0) | ||
| it := types.NewMapIterator(7, m) | ||
| require.Equal(t, types.NewIteratorType(types.TypeI64), it.Type()) | ||
| require.True(t, it.Done()) | ||
| } | ||
|
|
||
| func TestNewMapType(t *testing.T) { | ||
| t.Run("reference key and i64 value", func(t *testing.T) { | ||
| typ := NewMapType(TypeString, TypeI64) | ||
| require.Equal(t, TypeString, typ.Key) | ||
| require.Equal(t, TypeI64, typ.Elem) | ||
| require.Equal(t, KindRef, typ.KeyKind) | ||
| require.Equal(t, KindI64, typ.ElemKind) | ||
| typ := types.NewMapType(types.TypeString, types.TypeI64) | ||
| require.Equal(t, types.TypeString, typ.Key) | ||
| require.Equal(t, types.TypeI64, typ.Elem) | ||
| require.Equal(t, types.KindRef, typ.KeyKind) | ||
| require.Equal(t, types.KindI64, typ.ElemKind) | ||
| require.True(t, typ.TraceKeys) | ||
| require.True(t, typ.TraceValues) | ||
| }) | ||
|
|
||
| t.Run("primitive key and value", func(t *testing.T) { | ||
| typ := NewMapType(TypeI32, TypeI32) | ||
| typ := types.NewMapType(types.TypeI32, types.TypeI32) | ||
| require.False(t, typ.TraceKeys) | ||
| require.False(t, typ.TraceValues) | ||
| }) | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift
Remove direct representation checks from external tests.
The external test migration still validates internal storage fields. Replace these checks with public observable behavior.
types/map_test.go#L11-L79: Replace directTyp,Zero, and map-type field checks with public map behavior and type methods.types/struct_test.go#L15-L34: Replace directStruct.TypandStruct.Datachecks withTypeandFieldbehavior.types/struct_test.go#L126-L130: Replace directStructType.Fieldschecks with public field lookup behavior.program/parse_test.go#L15-L88: Replace direct program section field comparisons with serialization or public accessors.
As per coding guidelines, tests must not access private symbols or representations and must assert public observable behavior.
📍 Affects 3 files
types/map_test.go#L11-L79(this comment)types/struct_test.go#L15-L34types/struct_test.go#L126-L130program/parse_test.go#L15-L88
🤖 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 `@types/map_test.go` around lines 11 - 79, Remove representation-level
assertions from types/map_test.go lines 11-79 by testing public map behavior and
type methods instead of Typ, Zero, and map-type fields; update
types/struct_test.go lines 15-34 to use Struct.Type and Field behavior rather
than Struct.Typ/Data; update types/struct_test.go lines 126-130 to verify public
field lookup rather than StructType.Fields; update program/parse_test.go lines
15-88 to compare serialized output or use public accessors instead of direct
program section fields.
Source: Coding guidelines
Changes Made
Related Issues
Additional Information
Summary by CodeRabbit
Bug Fixes
Documentation
Tests