Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions crates/perry-codegen/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7022,6 +7022,27 @@ pub(crate) fn lower_expr(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
Ok(double_literal(0.0))
}

Expr::FsAppendFileSync(path, content) => {
// Issue #226. JS and WASM backends already had arms for this
// HIR variant; the LLVM backend was missing the case here next
// to `FsWriteFileSync`. The runtime fn
// (`crates/perry-runtime/src/fs.rs::js_fs_append_file_sync`)
// is correct; the codegen + lowerer plumbing was the gap.
// The companion fix in `crates/perry-hir/src/lower/expr_call.rs`
// extends the namespace-import path (`fs.appendFileSync` via
// `import * as fs from "fs"`) — without that the variant
// never gets emitted for the common usage shape. Returns i32
// (1=success) which we discard; appendFileSync is void in JS.
let p = lower_expr(ctx, path)?;
let c = lower_expr(ctx, content)?;
let _ = ctx.block().call(
I32,
"js_fs_append_file_sync",
&[(DOUBLE, &p), (DOUBLE, &c)],
);
Ok(double_literal(0.0))
}

// -------- NativeMethodCall (Phase H.1) --------
// Perry's HIR uses NativeMethodCall { module, method, object, args }
// for method calls on natively-typed receivers — specifically for
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-codegen/src/runtime_decls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ pub fn declare_phase_b_strings(module: &mut LlModule) {
module.declare_function("js_map_from_array", I64, &[I64]);
module.declare_function("js_object_has_property", DOUBLE, &[DOUBLE, DOUBLE]);
module.declare_function("js_fs_write_file_sync", I32, &[DOUBLE, DOUBLE]);
// fs.appendFileSync(path, content) — returns i32 status. Issue #226.
module.declare_function("js_fs_append_file_sync", I32, &[DOUBLE, DOUBLE]);
module.declare_function("js_fs_exists_sync", I32, &[DOUBLE]);
// fs.readFileSync(path, encoding) — returns a raw *mut StringHeader i64.
module.declare_function("js_fs_read_file_sync", I64, &[DOUBLE]);
Expand Down
8 changes: 8 additions & 0 deletions crates/perry-hir/src/lower/expr_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,14 @@ pub(super) fn lower_call(ctx: &mut LoweringContext, call: &ast::CallExpr) -> Res
return Ok(Expr::FsWriteFileSync(Box::new(path), Box::new(content)));
}
}
"appendFileSync" => {
if args.len() >= 2 {
let mut iter = args.into_iter();
let path = iter.next().unwrap();
let content = iter.next().unwrap();
return Ok(Expr::FsAppendFileSync(Box::new(path), Box::new(content)));
}
}
"existsSync" => {
if args.len() >= 1 {
return Ok(Expr::FsExistsSync(Box::new(args.into_iter().next().unwrap())));
Expand Down
42 changes: 42 additions & 0 deletions test-files/test_fs_appendfile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Issue #226: regression for `fs.appendFileSync` codegen.
//
// Two paths needed wiring (only one was in the issue's diagnosis):
//
// 1. SWC→HIR lowerer in `crates/perry-hir/src/lower/expr_call.rs`. The
// named-import path (`import { appendFileSync } from "fs"`) already
// had an arm; the namespace-import + property-access path
// (`import * as fs from "fs"; fs.appendFileSync(...)`) did not, so
// the call slipped past as a generic `Call { callee: PropertyGet
// { object: NativeModuleRef("fs"), property: "appendFileSync" } }`
// and `Expr::FsAppendFileSync` never got emitted.
//
// 2. LLVM codegen in `crates/perry-codegen/src/expr.rs`. Even when
// the HIR variant was emitted, the backend lacked the arm next to
// `FsWriteFileSync`, so the call fell through and was silently
// dropped. JS and WASM backends had had the arm for a while.
//
// Net pre-fix behaviour: every `fs.appendFileSync` wrote 0 bytes, the
// file wasn't even created on a fresh path. This test exercises the
// common namespace-import shape and asserts the file accumulates.

import * as fs from "fs";

const path = "/tmp/perry_appendfile_test.txt";

if (fs.existsSync(path)) {
fs.unlinkSync(path);
}

// First call — file doesn't exist yet, append-mode open creates it.
fs.appendFileSync(path, "line one\n");
console.log(fs.readFileSync(path, "utf-8"));

// Second call — should grow the file rather than overwrite.
fs.appendFileSync(path, "line two\n");
console.log(fs.readFileSync(path, "utf-8"));

// Third — accumulates.
fs.appendFileSync(path, "line three\n");
console.log(fs.readFileSync(path, "utf-8"));

fs.unlinkSync(path);
Loading