Skip to content

Commit 5ab8e3e

Browse files
committed
fix: wire up perry/ui ForEach codegen (v0.5.110) (#103 followup)
- ForEach(state, render) now synthesizes a VStack container, calls perry_ui_for_each_init(container, state, closure), and returns the container handle. Without this, the generic dispatch emitted a 'ForEach not in dispatch table' warning, returned 0, and the outer VStack's add_child got an invalid handle — app ran but no window (type="BackgroundOnly"). - Verified test_min4 and the rebuilt todo app launch as type="Foreground".
1 parent e329a64 commit 5ab8e3e

3 files changed

Lines changed: 49 additions & 2 deletions

File tree

CLAUDE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
88

99
Perry is a native TypeScript compiler written in Rust that compiles TypeScript source code directly to native executables. It uses SWC for TypeScript parsing and LLVM for code generation.
1010

11-
**Current Version:** 0.5.109
11+
**Current Version:** 0.5.110
1212

1313
## TypeScript Parity Status
1414

@@ -150,6 +150,7 @@ First-resolved directory cached in `compile_package_dirs`; subsequent imports re
150150

151151
Keep entries to 1-2 lines max. Full details in CHANGELOG.md.
152152

153+
- **v0.5.110** — Wire up `ForEach(state, render)` codegen in `perry-ui-macos` path (followup to #103). Previously `perry/ui` warned `method 'ForEach' not in dispatch table (args: 2)` and the generic fallback returned 0/undefined; the outer VStack's `widget_add_child` was then called with an invalid handle, AppKit silently refused to attach the window body, and the process ran with `lsappinfo type="BackgroundOnly"` — no window shown. New special case in `lower_call.rs` (next to `VStack`/`HStack`): synthesize a `perry_ui_vstack_create(8.0)` container, call `perry_ui_for_each_init(container, state_handle, render_closure)`, return the NaN-boxed container pointer. Verified: `test_min4.ts` (VStack containing ForEach over State<string[]>) and the rebuilt todo app both now launch with `type="Foreground"`. The `ForEach` UiSig isn't table-registered because it's variadic-in-shape (needs side-effectful container synthesis + for_each_init call + return), same pattern as VStack/HStack.
153154
- **v0.5.109** — Fix `perry init` TypeScript type stubs and the UI docs that exercised them (closes #103). `types/perry/ui/index.d.ts`: `State` is now generic (`State<T = number>`) so `State<string[]>([])` and `State("")` type-check instead of erroring on the old number-only signature; added `ForEach(count: State<number>, render)` export (was used in the todo-app docs example but missing from the stub); `stateBindTextfield` now takes `State<string>`. Rewrote the docs examples in `docs/src/getting-started/first-app.md` (counter + todo), `docs/src/ui/state.md` (two-way binding section, onChange snippet, complete example), `docs/src/ui/widgets.md` (TextField/SecureField/Toggle/Slider/Picker/Form), and `docs/src/ui/dialogs.md` to use the actual runtime signatures — `TextField(placeholder, onChange)`, `Slider(min, max, onChange)`, `Picker(onChange)` + `pickerAddItem`, etc. — instead of the fictional `TextField(state, placeholder)` / `count.onChange(...)` forms. The old forms silently segfaulted at launch because `UiArgKind::Str` at `lower_call.rs:2557` routes the first arg through `get_raw_string_ptr`, and a State handle (i64) reinterpreted as a NaN-boxed string derefs garbage. No runtime/codegen change — the stubs are embedded via `include_str!` at compile time and ship to users via `perry init` / `perry types`. Verified: 5 new example binaries (counter, todo, controls, form, onchange) all `tsc --noEmit` clean AND compile + launch without crashing.
154155
- **v0.5.108** — Honor `PERRY_RUNTIME_DIR` / `PERRY_LIB_DIR` env vars in `find_library` so out-of-tree installs (perry binary in `/usr/local/bin`, source tree elsewhere) can point at an explicit lib dir. The "Could not find libperry_runtime.a" error now lists every candidate path it searched and names the env var as a fix. Closes #101.
155156
- **v0.5.107** — First end-to-end release with npm distribution live. `@perryts/perry` + seven per-platform optional-dep packages (`@perryts/perry-{darwin-arm64,darwin-x64,linux-x64,linux-arm64,linux-x64-musl,linux-arm64-musl,win32-x64}`) publish via OIDC Trusted Publisher from `release-packages.yml` on each GitHub Release. `npx @perryts/perry compile file.ts` works on all seven platforms. No runtime/codegen change.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ opt-level = "s" # Optimize for size in stdlib
102102
opt-level = 3
103103

104104
[workspace.package]
105-
version = "0.5.109"
105+
version = "0.5.110"
106106
edition = "2021"
107107
license = "MIT"
108108
repository = "https://github.com/PerryTS/perry"

crates/perry-codegen/src/lower_call.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2315,6 +2315,52 @@ pub(crate) fn lower_native_method_call(
23152315
return Ok(nanbox_pointer_inline(blk, &parent_final));
23162316
}
23172317

2318+
// perry/ui ForEach — TS shape is `ForEach(state, (i) => Widget)`. The
2319+
// runtime's `perry_ui_for_each_init` wants `(container, state, closure)`,
2320+
// so we synthesize a VStack container, call for_each_init with it, and
2321+
// return the container handle. Without this special case the call falls
2322+
// through to the generic dispatch which emits the "method 'ForEach' not
2323+
// in dispatch table" warning and returns 0/undefined — the outer VStack
2324+
// then tries to add_child with an invalid handle, AppKit silently fails
2325+
// to attach the window body, and the process runs but no window shows.
2326+
if module == "perry/ui" && method == "ForEach" && object.is_none() && args.len() == 2 {
2327+
ctx.pending_declares.push((
2328+
"perry_ui_vstack_create".to_string(),
2329+
I64,
2330+
vec![DOUBLE],
2331+
));
2332+
ctx.pending_declares.push((
2333+
"perry_ui_for_each_init".to_string(),
2334+
crate::types::VOID,
2335+
vec![I64, I64, DOUBLE],
2336+
));
2337+
2338+
let spacing = "8.0".to_string();
2339+
let blk = ctx.block();
2340+
let container = blk.call(I64, "perry_ui_vstack_create", &[(DOUBLE, &spacing)]);
2341+
let container_slot = ctx.func.alloca_entry(I64);
2342+
ctx.block().store(I64, &container, &container_slot);
2343+
2344+
// args[0]: State handle — NaN-boxed pointer, unbox to i64.
2345+
let state_box = lower_expr(ctx, &args[0])?;
2346+
let blk = ctx.block();
2347+
let state_handle = unbox_to_i64(blk, &state_box);
2348+
2349+
// args[1]: render closure — stays as a NaN-boxed f64.
2350+
let closure_d = lower_expr(ctx, &args[1])?;
2351+
2352+
let blk = ctx.block();
2353+
let container_reload = blk.load(I64, &container_slot);
2354+
blk.call_void(
2355+
"perry_ui_for_each_init",
2356+
&[(I64, &container_reload), (I64, &state_handle), (DOUBLE, &closure_d)],
2357+
);
2358+
2359+
let blk = ctx.block();
2360+
let container_final = blk.load(I64, &container_slot);
2361+
return Ok(nanbox_pointer_inline(blk, &container_final));
2362+
}
2363+
23182364
// perry/ui Button — TS shape is `Button(label, handler)` where
23192365
// handler is a closure. The simple positional form is what mango
23202366
// uses. The Object-config form (`Button(label, { onPress: cb })`)

0 commit comments

Comments
 (0)