Skip to content

feat(perry-codegen-arkts): #669 Chart + #670 TreeView on HarmonyOS (v0.5.893) - #736

Merged
proggeramlug merged 3 commits into
mainfrom
feat/669-670-arkts-chart-treeview
May 13, 2026
Merged

feat(perry-codegen-arkts): #669 Chart + #670 TreeView on HarmonyOS (v0.5.893)#736
proggeramlug merged 3 commits into
mainfrom
feat/669-670-arkts-chart-treeview

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Summary

Test plan

  • cargo test --release -p perry-codegen-arkts — 109/109 unit tests pass (6 new), 2/2 integration pass
  • cargo test --release --workspace --exclude perry-ui-{ios,tvos,watchos,visionos,android,windows,gtk4} — exit 0
  • cargo build --release -p perry-codegen-arkts -p perry-runtime -p perry-stdlib -p perry — clean
  • Six new tests cover: chart bar with folded data + Canvas + ctx field + width/height + title; line kind emits stroke path; pie kind emits arc fill + legend; chartClearData drops earlier points; treeview round-trips recursive node literal + @State fields + flatten helper + onSelect dispatch; treeview depth-padding shape

proggeramlug added a commit that referenced this pull request May 13, 2026
Pre-existing flake in `readline::tests::has_active_reflects_state`:
the test pushes "x" into PENDING_LINES then asserts has_active==1, but
`injected_line_drains_via_test_helper`'s `reset()` (called by the
parallel runner) can clear PENDING_LINES in the window between those
two lines. All readline tests share global state (PENDING_LINES /
PENDING_DATA / EOF_REACHED / RAW_MODE / thread_local callback cells),
which cargo test's default parallel scheduler races on.

Fix: add a `TEST_LOCK: Mutex<()>` and have `reset()` acquire it +
return the guard. Tests that touch shared state now hold the guard
for the test's duration via `let _g = reset();`. Pure-function tests
(`parse_keypress_*`) don't call `reset()` and stay parallel-safe.

Unblocks PR #736 (issues #669/#670 — Chart + TreeView on HarmonyOS)
which was bouncing on this pre-existing flake.
…(v0.5.892)

Closes the last two widgets in the perry/ui parity matrix — ArkUI has no
native chart or tree-outline primitive, so both are hand-rolled.

Chart: `Chart(kind, w, h)` → `Canvas(this.__chart_<n>_ctx)` backed by
per-instance `RenderingContextSettings` + `CanvasRenderingContext2D`
fields on the @component. Four new `Mutation` variants
(`ChartAddDataPoint`/`ChartClearData`/`ChartSetTitle`/`ChartReload`)
fold the mutator stream at codegen time via `collect_mutations_in_expr`;
`fold_chart_mutations` reduces it to a (points, title) pair baked into
the `.onReady()` draw closure as `const data` + `const title`. Three
draw branches (line/bar/pie) mirror the Android `PerryChartView.onDraw`
math 1:1.

TreeView: `TreeView(root, onSelect)` → `List({space: 0})` with
`ForEach(this.__tree_<n>_flatten(), …)`. Reconstructs the static node
graph at codegen time by chasing `rootNode` through `bindings` to its
`TreeNode(id, label)` constructor + accumulated `treeNodeAddChild`
mutations (`Mutation::TreeAddChild`, `build_tree_node` with 16-hop
guard). Emits a `private __tree_<n>_nodes` constant + two `@State`
fields (expanded Set, selectedId string) + a `__tree_<n>_flatten()`
helper. Tap toggles the expanded set and dispatches `row.id` to
`perryEntry.invokeCallback1(<slot>, row.id)`.

Threading: bundles `chart_instances` + `tree_view_instances` into one
new `&mut HarvestExtras` param so future state-emitting widgets don't
each grow every recursive emit_* signature.

Tests: six new perry-codegen-arkts unit tests (chart bar/line/pie/clear
+ treeview graph/depth-padding); all 109 unit tests pass.
Pre-existing flake in `readline::tests::has_active_reflects_state`:
the test pushes "x" into PENDING_LINES then asserts has_active==1, but
`injected_line_drains_via_test_helper`'s `reset()` (called by the
parallel runner) can clear PENDING_LINES in the window between those
two lines. All readline tests share global state (PENDING_LINES /
PENDING_DATA / EOF_REACHED / RAW_MODE / thread_local callback cells),
which cargo test's default parallel scheduler races on.

Fix: add a `TEST_LOCK: Mutex<()>` and have `reset()` acquire it +
return the guard. Tests that touch shared state now hold the guard
for the test's duration via `let _g = reset();`. Pure-function tests
(`parse_keypress_*`) don't call `reset()` and stay parallel-safe.

Unblocks PR #736 (issues #669/#670 — Chart + TreeView on HarmonyOS)
which was bouncing on this pre-existing flake.
@proggeramlug
proggeramlug force-pushed the feat/669-670-arkts-chart-treeview branch from 338ad27 to f8a12b6 Compare May 13, 2026 04:32
@proggeramlug proggeramlug changed the title feat(perry-codegen-arkts): #669 Chart + #670 TreeView on HarmonyOS feat(perry-codegen-arkts): #669 Chart + #670 TreeView on HarmonyOS (v0.5.893) May 13, 2026
@proggeramlug
proggeramlug merged commit e8c4a95 into main May 13, 2026
9 checks passed
@proggeramlug
proggeramlug deleted the feat/669-670-arkts-chart-treeview branch May 13, 2026 05:08
proggeramlug added a commit that referenced this pull request May 13, 2026
…g (v0.5.895)

Two-site override (resolve.rs::resolve_import + collect_modules.rs) so a
package listed in `perry.compilePackages` is compiled from its
node_modules source instead of routed to the built-in Rust FFI binding.
Unblocks users of packages whose native binding is incomplete (e.g.
`rate-limiter-flexible`, where `perry-ext-ratelimit` only wires `consume`
through codegen and the codegen-declared `js_ratelimit_create(I64)`
doesn't match the real two-arg `js_ratelimit_new(points, duration_secs)`
impl).

Default behavior unchanged: stock projects without `compilePackages` go
through the exact same path. Opt-in and package-scoped.

Refs #665. Renumbered from v0.5.892 → v0.5.895 (parallel PRs #734/#736/#737
landed on main). See CHANGELOG.md for full root-cause notes.
proggeramlug added a commit that referenced this pull request May 13, 2026
…g (v0.5.895)

Two-site override (resolve.rs::resolve_import + collect_modules.rs) so a
package listed in `perry.compilePackages` is compiled from its
node_modules source instead of routed to the built-in Rust FFI binding.
Unblocks users of packages whose native binding is incomplete (e.g.
`rate-limiter-flexible`, where `perry-ext-ratelimit` only wires `consume`
through codegen and the codegen-declared `js_ratelimit_create(I64)`
doesn't match the real two-arg `js_ratelimit_new(points, duration_secs)`
impl).

Default behavior unchanged: stock projects without `compilePackages` go
through the exact same path. Opt-in and package-scoped.

Refs #665. Renumbered from v0.5.892 → v0.5.895 (parallel PRs #734/#736/#737
landed on main). See CHANGELOG.md for full root-cause notes.
proggeramlug added a commit that referenced this pull request May 13, 2026
…er-flexible work end-to-end (#735)

* feat(resolve): #665 — compilePackages overrides NATIVE_MODULES routing (v0.5.895)

Two-site override (resolve.rs::resolve_import + collect_modules.rs) so a
package listed in `perry.compilePackages` is compiled from its
node_modules source instead of routed to the built-in Rust FFI binding.
Unblocks users of packages whose native binding is incomplete (e.g.
`rate-limiter-flexible`, where `perry-ext-ratelimit` only wires `consume`
through codegen and the codegen-declared `js_ratelimit_create(I64)`
doesn't match the real two-arg `js_ratelimit_new(points, duration_secs)`
impl).

Default behavior unchanged: stock projects without `compilePackages` go
through the exact same path. Opt-in and package-scoped.

Refs #665. Renumbered from v0.5.892 → v0.5.895 (parallel PRs #734/#736/#737
landed on main). See CHANGELOG.md for full root-cause notes.

* fix(codegen): #665 — default-import collision no longer mints phantom cross-class method symbols (v0.5.896)

When two different classes are both default-imported in the same file,
the `class_ids` / `imported_class_prefix` / `imported_class_source_name`
side maps in `compile_module` overwrote each other (last-writer-wins)
while `class_table` kept the first stub (first-writer-wins). The
mismatch produced method symbols mangled with FIRST stub's method list
+ LAST writer's class name + LAST writer's source prefix — names the
linker can't resolve since Phase F only declares each ic's REAL
methods. Flip the three side-map `.insert()` calls to `.entry().or_insert()`
so all four maps agree on first-writer-wins.

Fixes the `@perry_method_..._RateLimiterRes_js__RateLimiterRes__delete`
/ `__get` / `__set` / `__block` undefined-value errors that surfaced
in rate-limiter-flexible's 5 leaf files once v0.5.895's routing opt-in
let them reach codegen.

Refs #665. See CHANGELOG.md for full root-cause walkthrough.

* fix(codegen,runtime): #665 — setTimeout(fn, delay, ...args) forwards trailing args (v0.5.897)

Existing `"setTimeout" if args.len() == 2` arm only handled the 2-arg
shape; 3+ arg call sites (e.g. `setTimeout(resolve, delay, res)` inside
Promise executors per ECMA-262 §27.5.4.1) fell through to the generic
ExternFuncRef fallthrough and emitted `call @settimeout(...)` against a
symbol nothing in stdlib defines. Discovering call site:
`RateLimiterMemory.consume()`'s Promise executor in rate-limiter-flexible.

Adds:
- `CallbackTimer::args: Vec<f64>` (empty for 2-arg shape — back-compat).
- `js_set_timeout_callback_args(callback, delay_ms, args_ptr, n_args)`
  runtime entry that copies the buffer in.
- `js_callback_timer_tick` dispatch via `match args.len()` to
  `js_closure_call0..js_closure_call9`, clamping at 9 trailing args.
- Codegen `"setTimeout" if args.len() >= 3` arm: alloca a stack buffer
  of doubles for the trailing args, GEP for the base, call the new
  runtime entry.

Refs #665. See CHANGELOG.md for full root-cause + scope notes.

* fix(hir): #665 — compilePackages-opted-in packages skip native-instance lowering (v0.5.898)

HIR's `is_native_module` only consulted the NATIVE_MODULES manifest, so
`rate-limiter-flexible` was lowered as a native import regardless of the
user's `compilePackages` opt-in. That cascaded:

  register_native_module(local, "rate-limiter-flexible", ...)
    → new RateLimiterMemory(...) triggers
  register_native_instance(limiter, "rate-limiter-flexible", "RateLimiterMemory")
    → limiter.consume in expr_member::lower_member emits
  NativeMethodCall { module, object: Some(limiter), method: "consume", args: [] }
    → codegen routes to js_native_call_method → no FFI entry → 0.0

So `typeof limiter.consume === "number"`. For genuine native bindings
(req.method on http.IncomingMessage) this zero-arg-getter lowering is
correct; for compile-package-overridden classes it's wrong because
`consume` is a real method, not a getter.

Fix: add a thread-local `COMPILE_PACKAGES_OVERRIDE: HashSet<String>` in
perry-hir, set by the compiler driver via
`set_compile_packages_override(ctx.compile_packages.clone())` before
each `lower_module_full` invocation and cleared after.
`is_native_module` parses the package name (supports `node:` prefix +
`@scope/pkg` scoped form) and returns false when the package is in the
override even if it appears in NATIVE_MODULES.

Validation: rate-limiter-flexible probe now reports `typeof
limiter.consume: function` (was "number"). Workspace tests green.

Refs #665. See CHANGELOG.md for full root-cause walkthrough.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant