Part of #968.
Problem
Callback-style async fs (fs.readFile(path, cb), fs.writeFile, fs.stat, fs.mkdir, fs.readdir, … ~20 functions) works in the interpreter but does not compile. FsModuleEmitter.TryEmitMethod's switch only lists sync methods, streams, watchers, and the constants/promises property-gets; the callback forms fall through to _ => false. This is a real interpreter↔compiled parity bug — code that runs interpreted silently fails to compile.
Reference (the interpreter already does this correctly)
FsModuleInterpreter.cs + FsAsyncHelpers.cs: extract callback → Ref() → Task.Run(real async BCL I/O) → on completion ScheduleTimer(0, …) to invoke the callback with marshalled (err, result) → Unref(). Callback is deferred to the next tick (Node-correct), never called synchronously.
The compiled event loop has the same capability, proven by timers: $EventLoop.Schedule(Action) + InvokeValue(callback, args) (RuntimeEmitter.TSEventLoop.cs, RuntimeEmitter.VirtualTimers.cs).
Scope
- Add callback-form dispatch in
FsModuleEmitter (detect trailing callback arg) for the full set the interpreter supports.
- Emit/route through a runtime helper that runs the op, then
$EventLoop.Schedule(() => InvokeValue(cb, [err, result])), with Ref/Unref bracketing.
- Normalize errors to Node shape (
err.code e.g. ENOENT) in the err argument.
Approach B note: if the primitive:fs facade lands first, this closes for free — the TS facade's callback wrapping compiles identically in both modes. Prefer doing the facade first; otherwise implement the emitter here.
Acceptance
- Dual-mode tests: every callback-async fs function produces identical observable results interpreted vs compiled, including error (
err.code) cases and next-tick ordering.
- No regression in
dotnet test / Test262 / conformance.
Part of #968.
Problem
Callback-style async fs (
fs.readFile(path, cb),fs.writeFile,fs.stat,fs.mkdir,fs.readdir, … ~20 functions) works in the interpreter but does not compile.FsModuleEmitter.TryEmitMethod's switch only lists sync methods, streams, watchers, and theconstants/promisesproperty-gets; the callback forms fall through to_ => false. This is a real interpreter↔compiled parity bug — code that runs interpreted silently fails to compile.Reference (the interpreter already does this correctly)
FsModuleInterpreter.cs+FsAsyncHelpers.cs: extract callback →Ref()→Task.Run(real async BCL I/O)→ on completionScheduleTimer(0, …)to invoke the callback with marshalled(err, result)→Unref(). Callback is deferred to the next tick (Node-correct), never called synchronously.The compiled event loop has the same capability, proven by timers:
$EventLoop.Schedule(Action)+InvokeValue(callback, args)(RuntimeEmitter.TSEventLoop.cs,RuntimeEmitter.VirtualTimers.cs).Scope
FsModuleEmitter(detect trailing callback arg) for the full set the interpreter supports.$EventLoop.Schedule(() => InvokeValue(cb, [err, result])), withRef/Unrefbracketing.err.codee.g.ENOENT) in theerrargument.Approach B note: if the
primitive:fsfacade lands first, this closes for free — the TS facade's callback wrapping compiles identically in both modes. Prefer doing the facade first; otherwise implement the emitter here.Acceptance
err.code) cases and next-tick ordering.dotnet test/ Test262 / conformance.