Skip to content

Commit e61f853

Browse files
committed
Preserve root callables for managed callbacks
1 parent 91fa5c8 commit e61f853

2 files changed

Lines changed: 37 additions & 1 deletion

File tree

PdVm.Runtime/PdVmProgramBase.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -713,12 +713,21 @@ private void EnterScriptCallable(
713713
throw new InvalidOperationException("callable parameter layout does not match its arity");
714714
}
715715

716+
// Managed callbacks start after the root frame has halted, so there is
717+
// no active caller frame from which to inherit callable locals. The
718+
// root locals remain the lexical environment for the program and may
719+
// contain closure values referenced by the callback body (for example,
720+
// an event wrapper calling another RSS function). Preserve those
721+
// callable slots just as we do for a nested RSS call.
716722
var inheritedCallables = GetActiveFrameOrDefault() is { } caller
717723
? _locals.Skip(caller.LocalBase).Take(caller.LocalCount)
718724
.Select((value, slot) => (value, slot))
719725
.Where(item => item.value.Kind == PdVmValueKind.Callable)
720726
.ToArray()
721-
: [];
727+
: _locals.Take(_rootLocalCount)
728+
.Select((value, slot) => (value, slot))
729+
.Where(item => item.value.Kind == PdVmValueKind.Callable)
730+
.ToArray();
722731
var localBase = _locals.Count;
723732
_locals.AddRange(Enumerable.Repeat(PdVmValue.Null(), prototype.FrameLocalCount));
724733
InitializeRootCallableBindings(localBase, prototype.FrameLocalCount);

PdVm.Tests/PdVmTypedDotNetInteropTests.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,33 @@ public async Task CallbackQueuePreservesFifoOrderAndBorrowedCaptureState()
148148
Assert.Throws<ObjectDisposedException>(() => callback.Post(PdVmValue.FromInt(4)));
149149
}
150150

151+
[Fact]
152+
public async Task ManagedCallbackCanCallAClosureKeptInRootLocals()
153+
{
154+
using var fixture = new SourceFixture(
155+
"let mut calls: int = 0;\n" +
156+
"fn inner() -> int { calls = calls + 1; calls }\n" +
157+
"pub fn wrapper() -> int { inner() }\n");
158+
var output = PdVmDotNetSourceCompiler.CompileFile(fixture.SourcePath, fixture.OutputPath);
159+
var program = Assert.IsAssignableFrom<IPdVmCallableProgram>(
160+
PdVmAssemblyLoader.CreateProgram(Assembly.Load(File.ReadAllBytes(output))));
161+
var host = PdVmDefaultHost.CreateConsoleHost();
162+
_ = PdVmExecution.Run(program, host);
163+
var adapter = PdVmCallbackAdapters.Create<PdVmUnit, PdVmValue>(
164+
_ => Array.Empty<PdVmValue>(),
165+
value => value,
166+
Array.Empty<PdVmValueType>(),
167+
PdVmValueType.Int);
168+
using var callback = program.CreateCallback(
169+
"wrapper",
170+
adapter,
171+
host);
172+
173+
var result = await callback.InvokeAsync(PdVmUnit.Value);
174+
175+
Assert.Equal(1, result.AsInt());
176+
}
177+
151178
[Fact]
152179
public async Task CallbackAdapterSchemaIsValidatedBeforeScriptExecution()
153180
{

0 commit comments

Comments
 (0)