Skip to content

feat: add lambdas, native functions and the VM call-back bridge#85

Merged
kwakker35 merged 3 commits into
mainfrom
feat/vm-lambdas-and-natives
Jun 22, 2026
Merged

feat: add lambdas, native functions and the VM call-back bridge#85
kwakker35 merged 3 commits into
mainfrom
feat/vm-lambdas-and-natives

Conversation

@kwakker35

@kwakker35 kwakker35 commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

Sprint 5 Increment C. Lambdas become first-class values and flow through the native-function machinery that consumes them.

Lambdas compile to a BytecodeFunction via a sub-compiler that shares the root const cache. Free identifiers resolve by D-296 categories 1-3 only: top-level const inlined, readonly/mutable read or written as globals. Block-body lambdas return their implicit last expression with return as a lambda-local early exit (D-276). Upvalue capture (category 4) is deferred to Increment D — no upvalue opcodes emitted.

NativeFunction and the VmInvoker delegate live in Grob.Core so both the type checker and the VM can see them. The VM Call arm dispatches NativeFunction and BytecodeFunction transparently. The load-bearing piece is the re-entrant call-back bridge: ip/activeChunk/stackBase are promoted to VM instance fields and InvokeCallable drives a shared RunDispatch so a native can invoke a lambda argument back through the VM, holding the Increment-A frame discipline across the boundary.

The four array higher-order methods land as natives bound at GetProperty time: filter, select, sort (stable, key-selector, descending) and each. sort orders int/float/string/bool keys.

Folds in the D-319 cooperative-cancellation seam: the run entry takes a CancellationToken (default None = unlimited) and the dispatch loop checks it on a masked interval. The step counter is a VM-instance field, so the budget is continuous across the bridge — a runaway lambda invoked by a native is caught, not only a top-level loop. Cancellation surfaces as OperationCanceledException, outside the GrobError hierarchy and so uncatchable by a Grob catch.

Corrects the section 6 acceptance prose from "filter, map, sort" to "filter, select, sort" (D-280 removed map).

Three faults in this increment's own (uncommitted) code, found by the new integration tests, are fixed here: the RunDispatch Return arm terminated the whole script when a top-level call returned to frame 0 (top-level vs re-entrant dispatch are now distinguished); the sort comparer passed line 0 to GrobRuntimeException, which requires line

= 1; and a comparer fault wrapped by the sort helper in
InvalidOperationException is now unwrapped to the GrobRuntimeException.

No new opcodes, no parser or AST edits. Coverage stays above 90% on the affected projects (GrobValueComparer 100%, ArrayNatives 98%, VirtualMachine 94.6%, modified compiler files 92-98%).

Implements D-115, D-276, D-280, D-281, D-296, D-319.

Summary by CodeRabbit

Release Notes

  • New Features

    • Lambda expressions are now supported for array operations: filter, select, sort, and each (including correct handling of block-bodied lambdas).
    • Added cooperative cancellation support (step-budgeted) during execution.
    • Added native function registration to extend VM globals.
  • Bug Fixes

    • Improved Unknown handling in type-checking for unary arithmetic, lambda/predicate validation, and assignments.
    • Enhanced sort/comparison error surfacing for key-type issues.
  • Documentation

    • Updated Sprint 5 acceptance criteria for lambda construct requirements.

Sprint 5 Increment C. Lambdas become first-class values and flow
through the native-function machinery that consumes them.

Lambdas compile to a BytecodeFunction via a sub-compiler that shares
the root const cache. Free identifiers resolve by D-296 categories
1-3 only: top-level const inlined, readonly/mutable read or written
as globals. Block-body lambdas return their implicit last expression
with return as a lambda-local early exit (D-276). Upvalue capture
(category 4) is deferred to Increment D — no upvalue opcodes emitted.

NativeFunction and the VmInvoker delegate live in Grob.Core so both
the type checker and the VM can see them. The VM Call arm dispatches
NativeFunction and BytecodeFunction transparently. The load-bearing
piece is the re-entrant call-back bridge: ip/activeChunk/stackBase
are promoted to VM instance fields and InvokeCallable drives a shared
RunDispatch so a native can invoke a lambda argument back through the
VM, holding the Increment-A frame discipline across the boundary.

The four array higher-order methods land as natives bound at
GetProperty time: filter, select, sort (stable, key-selector,
descending) and each. sort orders int/float/string/bool keys.

Folds in the D-319 cooperative-cancellation seam: the run entry takes
a CancellationToken (default None = unlimited) and the dispatch loop
checks it on a masked interval. The step counter is a VM-instance
field, so the budget is continuous across the bridge — a runaway
lambda invoked by a native is caught, not only a top-level loop.
Cancellation surfaces as OperationCanceledException, outside the
GrobError hierarchy and so uncatchable by a Grob catch.

Corrects the section 6 acceptance prose from "filter, map, sort" to
"filter, select, sort" (D-280 removed map).

Three faults in this increment's own (uncommitted) code, found by the
new integration tests, are fixed here: the RunDispatch Return arm
terminated the whole script when a top-level call returned to frame 0
(top-level vs re-entrant dispatch are now distinguished); the sort
comparer passed line 0 to GrobRuntimeException, which requires line
>= 1; and a comparer fault wrapped by the sort helper in
InvalidOperationException is now unwrapped to the GrobRuntimeException.

No new opcodes, no parser or AST edits. Coverage stays above 90% on
the affected projects (GrobValueComparer 100%, ArrayNatives 98%,
VirtualMachine 94.6%, modified compiler files 92-98%).

Implements D-115, D-276, D-280, D-281, D-296, D-319.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 42a50df6-40d8-45a7-a387-c976aa569954

📥 Commits

Reviewing files that changed from the base of the PR and between ceac465 and c42d7a6.

📒 Files selected for processing (6)
  • src/Grob.Compiler/Compiler.Expressions.cs
  • src/Grob.Vm/ArrayNatives.cs
  • tests/Grob.Compiler.Tests/CompilerLambdaTests.cs
  • tests/Grob.Compiler.Tests/TypeCheckerLambdaTests.cs
  • tests/Grob.Vm.Tests/VirtualMachineCancellationTests.cs
  • tests/Grob.Vm.Tests/VirtualMachineNativeTests.cs
💤 Files with no reviewable changes (1)
  • tests/Grob.Vm.Tests/VirtualMachineCancellationTests.cs
🚧 Files skipped from review as they are similar to previous changes (5)
  • tests/Grob.Compiler.Tests/CompilerLambdaTests.cs
  • src/Grob.Compiler/Compiler.Expressions.cs
  • src/Grob.Vm/ArrayNatives.cs
  • tests/Grob.Compiler.Tests/TypeCheckerLambdaTests.cs
  • tests/Grob.Vm.Tests/VirtualMachineNativeTests.cs

📝 Walkthrough

Walkthrough

Implements Sprint 5 Increment C for the Grob language: lambda expression compilation into nested BytecodeFunction chunks, full lambda and array higher-order method type-checking (filter, select, sort, each), NativeFunction/VmInvoker core contracts, ArrayNatives runtime implementations, a re-entrant VM↔native call bridge, cooperative cancellation via a step-budget seam, and a RegisterNative API — all accompanied by compiler, type-checker, VM, and integration tests.

Changes

Sprint 5C: Lambdas, Array HOFs, Native Bridge & Cancellation

Layer / File(s) Summary
NativeFunction and VmInvoker contracts
src/Grob.Core/NativeFunction.cs, src/Grob.Core/VmInvoker.cs, tests/Grob.Core.Tests/RuntimeTypesTests.cs
NativeFunction is added as a sealed GrobFunction subclass backed by a Func<GrobValue[], VmInvoker, GrobValue> delegate; VmInvoker is added as the public re-entrant callback delegate. RuntimeTypesTests adds six coverage tests for construction, round-tripping, ToString, and argument guards.
TypeChecker: Unknown propagation, lambda inference, and array HOF validation
src/Grob.Compiler/TypeChecker.cs, src/Grob.Compiler/TypeChecker.Expressions.cs, src/Grob.Compiler/TypeChecker.Statements.cs
_lambdaReturnTypes dictionary added for reference-identity lambda return inference. VisitUnary and ResolveArithmetic propagate Unknown without error. VisitAssignment skips assignability check for Unknown value types. VisitLambda/VisitLambdaBlock fully implemented. VisitCall routes array higher-order methods to ValidateArrayMethodCall, emitting E0004 for non-bool filter predicates and non-bool sort flags.
Compiler: lambda bytecode emission and arithmetic refinement
src/Grob.Compiler/Compiler.Expressions.cs
VisitLambda compiles lambdas via a nested Compiler with shared _constValues, mapping parameters to initial local slots and emitting a BytecodeFunction constant. CompileLambdaBlock implements D-276 last-statement implicit-return semantics. Arithmetic opcode selection gains Unknown-aware baseType inference. IsBuiltinVoidCall helper routes print/exit through the safety-net path.
VM: instance-state dispatch, cancellation budget, and RegisterNative
src/Grob.Vm/VirtualMachine.cs
Dispatch state (_activeChunk, _ip, _stackBase, _cancellationToken, _steps) moved to instance fields for re-entrant bridging. Run gains an optional CancellationToken; a BudgetMask-gated ThrowIfCancellationRequested() is inserted into the dispatch loop. RegisterNative added. All opcode arms updated from local-variable to instance-field reads.
VM: Call/Return native dispatch and InvokeCallable re-entrant bridge
src/Grob.Vm/VirtualMachine.cs
OpCode.Call dispatches NativeFunction directly with a VmInvoker thread-back. OpCode.Return distinguishes top-level from re-entrant completion and restores saved context. GetProperty binds array HOF methods, capturing _cancellationToken. InvokeCallable saves/restores dispatch context for re-entrant bytecode calls and fast-paths nested native invocations.
ArrayNatives: filter, select, sort, each, and GrobValueComparer
src/Grob.Vm/ArrayNatives.cs
ArrayNatives.GetMethod returns bound NativeFunction instances for filter, select, sort, and each, capturing the receiver array and VmInvoker. sort unwraps LINQ InvalidOperationException to surface inner GrobRuntimeException. GrobValueComparer enforces kind equality and typed ordering for Int, Float, String, Bool keys.
Compiler lambda bytecode tests
tests/Grob.Compiler.Tests/CompilerLambdaTests.cs
CompilerLambdaTests inspects bytecode shape for expression-body and block-body lambdas (including empty blocks); validates parameter slot mapping and const inlining; verifies last-expression preservation in block bodies and implicit-return safety-net emission.
TypeChecker lambda and array-HOF tests
tests/Grob.Compiler.Tests/TypeCheckerLambdaTests.cs
TypeCheckerLambdaTests asserts the §3.1.1 Declaration invariant for all identifiers (including inside lambda bodies); validates clean type-checking for filter, select, sort, each, and chaining; verifies E0004 emission for non-bool filter predicates and non-bool sort descending flags.
Integration pipeline tests
tests/Grob.Integration.Tests/Sprint5IncrementCTests.cs
Sprint5IncrementCTests runs end-to-end pipeline tests covering filter (element selection, all-excluded, all-included, captured const/readonly), sort (ascending by key), each (printing, mutable mutation, empty arrays), chaining (filter→each, filter→filter), and block-body lambdas with implicit last-expression returns.
VM native dispatch and array-HOF runtime tests
tests/Grob.Vm.Tests/VirtualMachineNativeTests.cs
VirtualMachineNativeTests hand-builds bytecode to exercise NativeFunction dispatch, native-to-VM callback bridging via VmInvoker, RegisterNative integration, all array higher-order methods (filter, select, sort with int/string/float/bool keys and descending flag, each), sort error paths (mismatched/incomparable key types), and cancellation propagation through the native bridge.
VM cooperative-cancellation step-budget tests
tests/Grob.Vm.Tests/VirtualMachineCancellationTests.cs
VirtualMachineCancellationTests verifies the D-319 step-budget seam: pre-cancelled token, token cancelled mid-run, and default token (no cancellation); includes a type-invariant guard ensuring OperationCanceledException is uncatchable by Grob catch logic.
Design document update
docs/design/grob-v1-requirements.md
Sprint 5 acceptance criteria updated: mapselect in the list of constructs that lambdas work in.

Sequence Diagram(s)

sequenceDiagram
  participant GrobSource
  participant TypeChecker
  participant Compiler
  participant VirtualMachine
  participant NativeFunction
  participant InvokeCallable

  GrobSource->>TypeChecker: arr.filter(x => x > 0)
  TypeChecker->>TypeChecker: VisitLambda — register params, infer return type → _lambdaReturnTypes
  TypeChecker->>TypeChecker: ValidateArrayMethodCall — check predicate return is Bool
  TypeChecker-->>Compiler: typed AST

  Compiler->>Compiler: VisitLambda — nested Compiler, emit BytecodeFunction constant
  Compiler-->>VirtualMachine: Chunk with BytecodeFunction + GetProperty("filter") + Call

  VirtualMachine->>VirtualMachine: OpCode.GetProperty("filter") — bind NativeFunction capturing _cancellationToken
  VirtualMachine->>NativeFunction: OpCode.Call — extract args, thread VmInvoker
  loop for each element
    NativeFunction->>InvokeCallable: VmInvoker(lambdaFn, [element])
    InvokeCallable->>VirtualMachine: RunDispatch(isReentrant=true)
    VirtualMachine-->>InvokeCallable: predicate GrobValue result
    InvokeCallable-->>NativeFunction: result
  end
  NativeFunction-->>VirtualMachine: filtered GrobValue array
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • grob-lang/grob#41: Establishes the core VirtualMachine dispatch loop and VM stack foundations that this PR extends with instance-state re-entrancy, native dispatch, and cancellation budgeting.
  • grob-lang/grob#79: Introduces OpCode.Call/OpCode.Return and call-frame management that this PR directly modifies to support native↔VM re-entrant bridging.
  • grob-lang/grob#68: Adds OpCode.GetProperty handling for arrays that this PR extends to bind array higher-order method NativeFunction instances.

Poem

A lambda descends through a bytecode abyss,
Each element mapped, filtered — nothing amiss.
The VM re-enters itself, cool and calm,
A step-budget seam like a firewall balm.
filter, sort, each — the array brigade,
Sprint 5C complete, and the tests all cascade! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title follows Conventional Commits format with 'feat' prefix and provides a clear, accurate summary of the main changes: introducing lambdas, native functions, and VM re-entrant call-back infrastructure.
Docstring Coverage ✅ Passed Docstring coverage is 45.08% which is sufficient. The required threshold is 10.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/vm-lambdas-and-natives

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 and usage tips.

Comment thread tests/Grob.Vm.Tests/VirtualMachineCancellationTests.cs Fixed
Comment thread tests/Grob.Compiler.Tests/TypeCheckerLambdaTests.cs Fixed
Comment thread src/Grob.Compiler/Compiler.Expressions.cs Fixed
Comment thread tests/Grob.Compiler.Tests/CompilerLambdaTests.cs Fixed
Comment thread tests/Grob.Compiler.Tests/CompilerLambdaTests.cs Fixed
Comment thread tests/Grob.Vm.Tests/VirtualMachineNativeTests.cs Fixed
Comment thread tests/Grob.Vm.Tests/VirtualMachineNativeTests.cs Fixed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
src/Grob.Vm/ArrayNatives.cs (1)

48-48: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Remove unnecessary collection-expression spread.

Line 48 spreads result into a collection expression ([.. result]), which creates an intermediate copy, then the GrobArray constructor spreads it again into its internal list (context snippet 2, line 11). Pass the List<GrobValue> directly as IEnumerable<GrobValue> to avoid the intermediate allocation.

♻️ Proposed fix to eliminate double-copy
-        return GrobValue.FromArray(new GrobArray([.. result]));
+        return GrobValue.FromArray(new GrobArray(result));
🤖 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 `@src/Grob.Vm/ArrayNatives.cs` at line 48, In the return statement with
GrobValue.FromArray and GrobArray constructor, remove the collection-expression
spread operator ([..]) when passing the result list. Instead of new
GrobArray([.. result]), pass result directly as new GrobArray(result) to avoid
creating an intermediate copy, since the GrobArray constructor already handles
converting the IEnumerable into its internal list representation.
🤖 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 `@tests/Grob.Compiler.Tests/CompilerLambdaTests.cs`:
- Around line 283-295: The test method EmptyBlockBodyLambda_EmitsNilAndReturn
claims to test a lambda with an empty block body according to its comment, but
the actual source code being compiled contains a non-empty block with the
statement `x > 0`. Remove this statement from inside the lambda block in the
CompileSource call to make it truly empty, changing the lambda from `x => { x >
0 }` to `x => {}` so the test actually exercises the empty-block-body code path
as intended.

In `@tests/Grob.Compiler.Tests/TypeCheckerLambdaTests.cs`:
- Around line 99-115: The test method
Lambda_BlockBody_AllIdentifiers_SatisfySection311Invariant can pass without
actually validating the block-body invariant because the bag variable is never
checked and the identifiers collection could be empty, causing the foreach loop
to never execute. Add an Assert.False call immediately after the
CollectIdentifiers call to verify that bag.HasErrors is false, then add an
Assert.NotEmpty or Assert.True(identifiers.Count > 0) assertion to ensure the
identifiers collection is not empty before the foreach loop, so the test
actually validates the intended invariant.
- Around line 168-175: The Sort_WithNonBoolDescendingFlag_EmitsE0004 test method
only asserts the error code but must also verify the exact 1-based line and
column numbers as part of the full diagnostic contract. Update the
Assert.Contains expression to check not only that d.Code equals "E0004" but also
verify d.Line and d.Column match the expected position where the non-bool
argument (42) appears in the test code. Apply the same fix to the other E0004
test referenced in lines 207-219 so both tests validate the complete diagnostic
specification.
- Around line 63-69: The test in the foreach loop over identifiers only
validates that Declaration is not null, but the §3.1.1 invariant requires both
Declaration and ResolvedType to be non-null after type-check. The misleading
comment suggests ResolvedType validation is optional for lambda parameters, but
GrobType.Unknown is still a valid non-null type value. Add an assertion to
verify that id.ResolvedType is not null alongside the existing Declaration
assertion, remove the comment that incorrectly exempts ResolvedType from
validation, and apply this same fix to the similar assertion blocks referenced
in lines 89-95, 112-114, and 275-276.

---

Nitpick comments:
In `@src/Grob.Vm/ArrayNatives.cs`:
- Line 48: In the return statement with GrobValue.FromArray and GrobArray
constructor, remove the collection-expression spread operator ([..]) when
passing the result list. Instead of new GrobArray([.. result]), pass result
directly as new GrobArray(result) to avoid creating an intermediate copy, since
the GrobArray constructor already handles converting the IEnumerable into its
internal list representation.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8795a03f-e070-4e9b-be82-8f6821bb5641

📥 Commits

Reviewing files that changed from the base of the PR and between febf987 and ceac465.

📒 Files selected for processing (15)
  • docs/design/grob-v1-requirements.md
  • src/Grob.Compiler/Compiler.Expressions.cs
  • src/Grob.Compiler/TypeChecker.Expressions.cs
  • src/Grob.Compiler/TypeChecker.Statements.cs
  • src/Grob.Compiler/TypeChecker.cs
  • src/Grob.Core/NativeFunction.cs
  • src/Grob.Core/VmInvoker.cs
  • src/Grob.Vm/ArrayNatives.cs
  • src/Grob.Vm/VirtualMachine.cs
  • tests/Grob.Compiler.Tests/CompilerLambdaTests.cs
  • tests/Grob.Compiler.Tests/TypeCheckerLambdaTests.cs
  • tests/Grob.Core.Tests/RuntimeTypesTests.cs
  • tests/Grob.Integration.Tests/Sprint5IncrementCTests.cs
  • tests/Grob.Vm.Tests/VirtualMachineCancellationTests.cs
  • tests/Grob.Vm.Tests/VirtualMachineNativeTests.cs

Comment thread tests/Grob.Compiler.Tests/CompilerLambdaTests.cs
Comment thread tests/Grob.Compiler.Tests/TypeCheckerLambdaTests.cs
Comment thread tests/Grob.Compiler.Tests/TypeCheckerLambdaTests.cs
Comment thread tests/Grob.Compiler.Tests/TypeCheckerLambdaTests.cs
Triage and fixes from the bot review of the Sprint 5C lambdas/natives
PR. Behaviour is unchanged; the production fixes remove dead code and
a redundant allocation, the rest strengthen tests.

Production:
- Compiler.EmitArithmetic: drop the always-false `rt == Float` sub-
  condition in the Unknown-baseType fallback. When baseType is Unknown
  both operands are non-float by construction, so the fallback can only
  be Int (CodeQL constant-condition; one of the two Sonar new issues).
- ArrayNatives.Filter: pass the result List straight to the GrobArray
  ctor (it takes IEnumerable and copies once) instead of a `[.. result]`
  spread that added a redundant intermediate array.

Tests:
- TypeCheckerLambdaTests: assert the type side of the §3.1.1 invariant
  too, via Assert.NotEqual(GrobType.Error, id.ResolvedType). ResolvedType
  is a non-nullable enum, so CodeRabbit's literal Assert.NotNull would be
  an xUnit2002 error; the Error-sentinel check is the correct value-type
  form, and a lambda parameter's inferred Unknown is a valid resolved
  value, not the error sentinel.
- TypeCheckerLambdaTests: the block-body invariant test no longer passes
  vacuously — assert bag.HasErrors is false and the identifier set is
  non-empty before iterating (also retires the unused-bag CodeQL note).
- TypeCheckerLambdaTests: both E0004 tests now assert the full diagnostic
  contract (code plus exact 1-based line and column), per tests/CLAUDE.md.
- CompilerLambdaTests: the empty-block-body test now compiles a genuinely
  empty block (x => {}) so it exercises the safety-net Nil + Return path
  it claims to; asserts the exact [Nil, Return] shape.
- Remove an unused OperationCanceledException local in the cancellation
  type-hierarchy test; convert four foreach-with-map/filter loops in test
  builders to Where/Select (CodeQL quality queries).

Addresses PR #85 review.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kwakker35

Copy link
Copy Markdown
Collaborator Author

Review triage — committed locally as 2778359 (lands on next push)

Worked through every CodeRabbit, SonarCloud and CodeQL finding. Build clean (0 warnings under TreatWarningsAsErrors), full solution green.

# Finding Source Disposition
1 EmitArithmetic constant condition (always-false rt == Float) CodeQL / Sonar Fixed — simplified to Unknown ? Int : baseType
2 ArrayNatives.Filter [.. result] double-copy CodeRabbit nitpick Fixed — pass the List to the GrobArray(IEnumerable) ctor directly
3 §3.1.1 type side unchecked (4 loops) CodeRabbit Fixed + pushed back on literalAssert.NotNull(ResolvedType) is xUnit2002 (non-nullable enum); used Assert.NotEqual(GrobType.Error, …), the correct value-type form
4 Block-body invariant test passes vacuously CodeRabbit + CodeQL Fixed — assert !bag.HasErrors + NotEmpty(identifiers)
5 E0004 tests assert code only CodeRabbit Fixed — full contract (code + exact 1-based line + column)
6 Empty-block test isn't empty (x > 0 present) CodeRabbit Fixed — genuinely empty x => {}, asserts [Nil, Return]
7 Unused cancelled local CodeQL Fixed — removed
8 Unused bag local CodeQL Fixed — now asserted (same test as #4)
9 4× foreach→Where/Select in test builders CodeQL Fixed — converted (off hot path, behaviour preserved)

One push-back, on the record: CodeRabbit's suggested Assert.NotNull(id.ResolvedType) cannot compile here — ResolvedType is a non-nullable GrobType enum, so xUnit2002 fires and the build fails under TreatWarningsAsErrors. The §3.1.1 type-side invariant is instead asserted as Assert.NotEqual(GrobType.Error, id.ResolvedType); a lambda parameter's inferred Unknown is a valid resolved value (concrete inference is Increment D), not an unset default.

The two SonarCloud new issues are covered by #1 (constant condition) and the dead-store fixes (#7/#8). Bots re-run on push, so a fresh pass will follow.

Comment thread tests/Grob.Compiler.Tests/CompilerLambdaTests.cs Fixed
Comment thread tests/Grob.Compiler.Tests/CompilerLambdaTests.cs Fixed
Comment thread tests/Grob.Vm.Tests/VirtualMachineNativeTests.cs
Follow-up to the first review pass, addressing the findings the bots
raised after it ran. Behaviour unchanged.

- Compiler.CompileLambdaBlock: drop the unused `endLine` parameter
  (Sonar S1172). The last-statement Return already uses the statement's
  own line; `endLine` was dead. This was the one remaining Sonar new
  issue after the first pass.
- CompilerLambdaTests.FirstLambdaConstant and the const-inlining test:
  the first pass's foreach→Where conversions left a map in the loop body,
  which CodeQL then re-flagged ("missed opportunity to use Select"). Both
  are now terminal LINQ pipelines (OfType+FirstOrDefault, and Any) that
  end the cascade rather than shifting it one statement further.

The third CodeQL "use Select" note (Each test) is a push-back, not a
fix: that loop's body calls the side-effecting `invoker` (it runs the
lambda through the VM and records the result), so projecting it into a
Select would be a side-effecting projection — an anti-pattern. The
explicit foreach is correct there.

Addresses PR #85 review (SonarCloud + CodeQL follow-up).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kwakker35

Copy link
Copy Markdown
Collaborator Author

Review triage (follow-up pass) — committed locally as c42d7a6 (lands on next push)

Second pass, addressing what the bots raised after the first round was pushed. Build clean (0 warnings), full solution green.

Finding Source Disposition
S1172 unused endLine param in CompileLambdaBlock SonarCloud (the 1 remaining new issue) Fixed — removed; the Return already uses the statement's own line
FirstLambdaConstant foreach maps in body CodeQL Fixed — terminal LINQ (OfType<BytecodeFunction>().FirstOrDefault() ?? throw), ends the cascade
const-inlining test foreach maps in body CodeQL Fixed — collapsed to terminal .Any(...)
Each test foreach maps to invoker(...) result CodeQL Push-backinvoker is side-effecting; a .Select projection would be a side-effecting anti-pattern

Note on the CodeQL chain: the two fixed ones were a cascade my own first-pass .Where() conversions triggered — extracting the filter left a map in the loop body, which CodeQL then re-flagged. Switching to terminal LINQ operators (FirstOrDefault/Any) ends the cascade instead of shifting it one statement on.

The four CodeRabbit follow-up replies on the first-pass threads were acknowledgements (all marked resolved) — no further action.

After this lands, SonarCloud new issues should reach 0 and the two fixed CodeQL notes clear; the Each push-back will remain as a deliberate, on-record decision.

@sonarqubecloud

Copy link
Copy Markdown

@kwakker35 kwakker35 merged commit d92ec3d into main Jun 22, 2026
19 checks passed
@kwakker35 kwakker35 deleted the feat/vm-lambdas-and-natives branch June 22, 2026 18:37
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