feat(fs): Node fs parity — node: imports, fs/promises, full method set (#122) - #123
Conversation
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 79 |
| Duplication | 0 |
🟢 Coverage 56.30% diff coverage · -0.05% coverage variation
Metric Results Coverage variation ✅ -0.05% coverage variation (-1.00%) Diff coverage ✅ 56.30% diff coverage Coverage variation details
Coverable lines Covered lines Coverage Common ancestor commit (f7af3ac) 28592 21777 76.16% Head commit (79ddce3) 28682 (+90) 21832 (+55) 76.12% (-0.05%) Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch:
<coverage of head commit> - <coverage of common ancestor commit>Diff coverage details
Coverable lines Covered lines Diff coverage Pull request (#123) 119 67 56.30% Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified:
<covered lines added or modified>/<coverable lines added or modified> * 100%
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
|
Update — extended to all backends. This now covers the full surface on every backend, not just the VM:
|
|
Callback form added — Final backend coverage
Tests: |
#122) Brings tish:fs toward Node `fs` / `fs/promises` parity on the bytecode VM (the default `tish run` backend). Resolver (shared across backends): - strip a leading `node:` so `node:fs` / `node:fs/promises` resolve like the bare forms; add the `fs/promises` subpath and the missing `tty` alias. Runtime (crates/tish_runtime/src/fs_ext.rs): - a Node-compatible fs surface with one `*_core` per op and a macro deriving the sync export (value / error object — tish convention) and the promise export (fulfilled/rejected Promise — Node's fs/promises convention). - methods: readFile(Bytes)/writeFile/appendFile/exists/stat/lstat/readdir/ mkdir/rm/rmdir/unlink/rename/copyFile/cp/realpath/readlink/truncate/ mkdtemp/access, a Node-like `Stats` (isFile/isDirectory/size/mtimeMs/…), and `constants` (F_OK/R_OK/W_OK/X_OK). - `fs` now implies `promise` (fs/promises returns settled promises). VM registration: - `tish:fs` gains the Node names (readFileSync/statSync/existsSync/…) and the new methods; existing tish names (readFile/readDir/fileExists/isDir) keep their current contracts as aliases. - new `tish:fs/promises` module exposes the async forms. Tests: integration fixtures exercise the sync surface via `node:fs` and the async surface via `node:fs/promises` (await + access-rejects-on-missing). Scope: this lands the resolver + VM backend (the default path). The native codegen (`tish build`) and the interpreter backend get the same surface, plus the Node callback forms, as follow-ups tracked in #122. Refs #122
Brings the same Node fs / fs/promises surface to the other two backends so all of `tish run` (vm + interp) and `tish build` (native) reach parity. Native codegen (tish build): - emit the Node-named + new sync methods and a tish:fs/promises module in the generated Rust (referencing tishlang_runtime::fs_ext directly). - emit the await machinery (await_promise / promise_object) for async programs whenever fs is enabled, not only http — so `await readFile(...)` from fs/promises compiles without the http stack. Interpreter (tish_eval): - bridge a Node-compatible fs surface to tishlang_runtime::fs_ext through value_convert (eval args -> core, call, core -> eval). Core promises convert to eval CorePromise, so fs/promises awaits work on interp too. `fs` now pulls in the runtime dep for the bridge. Tests: fs_parity now exercises the sync and fs/promises fixtures on BOTH the vm and interp backends; the native path is verified by building and running a binary that uses node:fs + node:fs/promises. Refs #122
The sync fs functions are now dual: when the last argument is a function they take the Node callback form — run the op, then invoke `cb(null, result)` on success or `cb(err, null)` on failure (synchronously, since the I/O is sync) — and otherwise return synchronously as before. So `fs.readFile(p, (err,data)=>…)`, `writeFile(p, data, cb)`, `stat(p, cb)`, `unlink(p, cb)`, … all work. The tish names readFile/writeFile/readDir/readFileBytes now route to the dual fs_ext functions too (sync behavior unchanged — scii's full suite stays green; no code reads writeFile's return). Backend coverage: the callback form works on the VM (the default `tish run`). Native codegen has an existing limitation lowering a multi-arg callback closure passed to a native fn, and the interpreter can't bridge an eval function to a core native — so callbacks are VM-only for now; sync + fs/promises remain on all backends. Tracked in #122. Test: fs_parity_callbacks exercises the (err, data) form and the error path (missing file -> non-null err) on the VM. Refs #122
Node's mkdtemp appends 6 random chars; the timestamp-hex suffix was both predictable (a temp-dir security smell — symlink/collision races) and collision-prone under rapid calls. Use a random base-36 suffix (~51 bits) with create_dir's exclusive semantics and a collision-retry loop. Adds rand as an fs-gated dep of tish_runtime (already in the tree via tish_builtins). Verified distinct/unique/secure; fs_parity + parity + native batch all green.
7e2eab3 to
5963eae
Compare
|
Rebased onto current
Ready for review/merge. |
|
Callback-form status update (re-tested on the rebased branch):
Sync + promises remain full on every backend (interp/vm/native). Net: callbacks now work on VM and native; only the interp callback form is outstanding — a bounded interp change, not a fundamental limit. |
…every backend Resolves the last gap in #122's callback surface. The native codegen limitation was already fixed upstream (multi-arg closure lowering); this closes the interpreter side. The interpreter binds fs to self-less `Value::Native` fns bridged to core `fs_ext`, and a core `Callable` can't re-enter the interpreter, so the callback was silently dropped (readFile(path,cb) fell back to the sync form). Fix: handle the Node callback convention in `call_func`'s Native arm, where `&self` is available — map the fs native (old alias or fsx bridge) to its core op via `fsx::callback_core`, run it on the non-callback args, split the Result into `(err, data)`, and invoke the callback with `self.call_func` (the same eval-level path map/filter/sort callbacks already use). Exposes the fs `*_core` ops as pub. readFile/writeFile/stat/… callbacks now fire identically on interp/vm/native (success + error paths). fs_parity callback test now asserts all three backends; parity 95/0, native batch green.
|
Interp callback form now works — full parity on every backend. Fixed the last gap. How (interp): a self-less The |
Refs #122. Brings
tish:fstoward Nodefs/fs/promisesparity on the bytecode VM (the defaulttish runbackend).Specifiers (resolver)
node:prefix is stripped →node:fs/node:fs/promisesresolve like the bare formsfs/promisessubpath; added the missing barettyaliasMethods (
crates/tish_runtime/src/fs_ext.rs)One
*_coreper op + a macro deriving the sync export (value / error-object — tish convention) and the promise export (fulfilled/rejectedPromise— Node'sfs/promisesconvention):readFile/readFileBytes,writeFile,appendFile,exists,stat/lstat(Node-likeStats:isFile()/isDirectory()/size/mtimeMs/…),readdir,mkdir,rm/rmdir/unlink,rename,copyFile/cp,realpath,readlink,truncate,mkdtemp,access, plusconstants(F_OK/R_OK/W_OK/X_OK).Naming
Node names are primary (
readFileSync,statSync,existsSync, …); the existing tish names (readFile,readDir,fileExists,isDir) are kept as aliases with their current contracts, so existing code (e.g. scii) is unaffected.Async
tish:fs/promisesexposes the async forms;fsnow implies thepromisefeature (settled-promise machinery). All shipped/CI builds use--features full(already haspromiseviahttp), so the feature change is a no-op there.Tests
Integration fixtures exercise the sync surface via
node:fs(write/append/read/stat/mkdir/copy/readdir/rename/rm + constants) and the async surface vianode:fs/promises(await+accessrejecting on a missing path). ExistingreadFileBytestest and the full scii suite stay green.Scope
This PR lands the resolver + VM backend — the default
tish runpath — with full sync +fs/promisesparity. The native codegen (tish build), the interpreter backend, and the Node callback forms get the same surface as follow-ups, tracked in #122.