Skip to content

Commit ae0fea2

Browse files
committed
refactor(compile): mass split lower_call + compile.rs (v0.5.340)
Four extractions in one PR. Completes the lower_call.rs split (the last v0.5.339 deferred item) and four big chunks of compile.rs. lower_call/native.rs (805 LOC) — `lower_native_method_call` for native module dispatch (mysql2, pg, redis, mongo, ws, fastify, fetch, perry/ui, perry/system, perry/i18n, perry/plugin, AbortController). Required bumping 14 helpers to `pub(super)`. compile/targets.rs (~800 LOC) — 6 `compile_for_*` widget/web/wasm orchestrators plus their Apple/Android helpers (find_watchos_swift_runtime, find_visionos_swift_runtime, apple_sdk_version, lookup_bundle_id_from_toml, compile_metallib_for_bundle, generate_js_bundle). compile/object_cache.rs (~440 LOC) — V2.2 codegen cache: djb2_hash + Djb2Hasher + compute_object_cache_key (246 LOC) + ObjectCache struct + impl + 282-LOC test mod. compile/resolve.rs (~810 LOC) — TS/JS module resolution: resolve_import family + npm package classification + file: deps (#209) + find_perry_workspace_root + file-extension predicates. Cumulative across this commit: - compile.rs: 8057 → 5795 LOC (-2262, -28%) - lower_call.rs: 5085 → 4681 LOC (-404, -8%) Cumulative across the entire session (v0.5.329 → v0.5.340): - compile.rs: 9391 → 5795 (-38%) - lower_call.rs: 7000+ → 4681 (-33%) - lower.rs: 13591 → 7554 (-44%) - lower::lower_expr: 6687 → 624 (-91%) What's still inline in compile.rs (5795 LOC): the orchestrator entry points, context construction, build_optimized_libs (~270 LOC), build_link_command (the bulk). Each independently extractable; doing more would shift this PR into rewrite-territory. Verified: - cargo build --release clean - cargo test --workspace 434/0 = baseline - gap tests 25/28; doc-tests 80/82 = baseline - UI smoke (App + Button + Text) compiles to 0.9 MB binary, exits 0 in PERRY_UI_TEST_MODE; strip-dedup output matches v0.5.331 exactly (419 → 35 trimmed objects) — proves the live path through library_search + targets + strip_dedup + ObjectCache + resolve all works. - Multi-module #212 closure-capture smoke matches Node byte-for-byte.
1 parent 69f2584 commit ae0fea2

9 files changed

Lines changed: 3506 additions & 3356 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.339
11+
**Current Version:** 0.5.340
1212

1313
## TypeScript Parity Status
1414

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

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

152+
- **v0.5.340** — Tier 2.1 + 2.2 mass follow-up: completes the lower_call.rs split with the 805-LOC `lower_native_method_call` that v0.5.339 deferred, plus the next four big chunks of compile.rs (per-target codegen orchestrators, on-disk object cache, npm/module resolution). Four extractions in one PR. **lower_call.rs `lower_native_method_call`** (1 new sub-module, 805 LOC moved): `lower_call/native.rs` is the giant dispatcher routing `obj.method(args)` calls against native modules (mysql2, pg, redis, mongo, ws, fastify, fetch, perry/ui, perry/system, perry/i18n, perry/plugin, AbortController, …). Required bumping 14 helpers in `lower_call.rs` from private `fn` to `pub(super) fn` so the new sub-module can reach them via `super::` (perry_*_table_lookup family — UI / UI_INSTANCE / SYSTEM / I18N / PLUGIN / PLUGIN_INSTANCE — plus native_module_lookup, lower_perry_ui_table_call, lower_fetch_native_method, lower_abort_controller_call, lower_notification_schedule, find_outer_writes_stmt, is_abort_controller_expr, lower_native_module_dispatch, collect_closure_introduced_ids). The fn itself is `pub(crate)` (re-exported from lower_call.rs for callers in `crate::expr` that import directly). **compile.rs per-target orchestrators** (1 new sub-module, ~800 LOC moved): `compile/targets.rs` contains the 6 `compile_for_*` widget/web/wasm functions (ios_widget, watchos_widget, android_widget, wearos_tile, web, wasm) plus their supporting helpers (`generate_js_bundle`, `find_watchos_swift_runtime`, `find_visionos_swift_runtime`, `apple_sdk_version`, `lookup_bundle_id_from_toml`, `compile_metallib_for_bundle`). 12 fns hoisted to `pub(super)`. **compile.rs object cache** (1 new sub-module, ~440 LOC moved): `compile/object_cache.rs` contains `djb2_hash` + `Djb2Hasher` (the streaming hash accumulator) + `compute_object_cache_key` (the 246-LOC opts→key derivation) + the `ObjectCache` struct + impl + the 282-LOC `object_cache_tests` mod. Cluster moved together because they all relate to the V2.2 `.perry-cache/objects/<target>/<key>.o` codegen cache layout. `djb2_hash` re-exported as `pub` (used elsewhere in compile.rs); `compute_object_cache_key` is now `pub fn` so the resolve.rs sub-module's `cached_resolve_import` can keep calling it. **compile.rs resolve_import family** (1 new sub-module, ~810 LOC moved): `compile/resolve.rs` is the entire TypeScript / JavaScript module-resolution machinery: `find_perry_workspace_root` (workspace-marker walk), `find_node_modules` (walk-up search), `find_file_dep_in_package_json` (issue #209 file: dep resolution), `parse_package_specifier` / `resolve_with_extensions` / `resolve_package_entry` / `resolve_package_source_entry` / `resolve_exports` (per-segment resolution), `resolve_import` + `cached_resolve_import` (public entry + cache), `discover_extension_entries`, `compute_module_prefix`, plus the npm-package classification helpers (`has_perry_native_library`, `has_perry_native_module`, `parse_native_library_manifest`, `is_in_perry_native_package`, `extract_compile_package_dir`, `is_in_compile_package`) and the file-extension predicates (`is_js_file`, `is_ts_file`, `is_declaration_file`). 17 fns hoisted to `pub(super)` or `pub`. **Cumulative deltas this commit**: lower_call.rs 5085 → 4681 LOC (-404, ~8%); compile.rs 8057 → 5795 LOC (-2262, ~28%). **Cumulative across the entire session** (v0.5.329-v0.5.340): compile.rs 9391 → 5795 LOC (~38% reduction), lower_call.rs 7000+ → 4681 LOC (~33%), lower.rs 13591 → 7554 LOC (~44%), `lower::lower_expr` (the most-cited monolith) 6687 → 624 LOC (~91%). The single biggest cognitive-load reduction in the project's history. **What's still inline in compile.rs** (5795 LOC): the orchestrator entry points (`run`, `run_with_parse_cache`), context construction (`CompilationContext::new`), library/runtime building (`build_optimized_libs`, ~270 LOC), the link command construction (`build_link_command` and the inline body of `run_with_parse_cache`, the bulk of remaining LOC), and the v0.5.295 `find_clang` + Linux LLVM-prefix fallback. Each is independently extractable but doing more would shift this PR into rewrite-territory. **Verified**: cargo build --release clean; cargo test --workspace 434/0 = baseline; gap tests 25/28 = baseline; doc-tests --skip-xcompile 80/82 = baseline; comprehensive UI smoke (`App({ title, body: VStack([Text("OK"), Button("Click", () => {})]) })`) compiles to 0.9 MB binary, exits 0 in `PERRY_UI_TEST_MODE=1`, and the strip-dedup output (419 → 35 trimmed objects) matches v0.5.331 exactly — proves the live path through library_search + targets + strip_dedup + ObjectCache + resolve all works end-to-end. Multi-module #212 closure-capture smoke matches Node byte-for-byte. **Cumulative across this session** (v0.5.329-v0.5.340, twelve commits): every plan item delivered as full or pilot work, plus three rounds of follow-up extractions completing most of the cognitive-load reductions the plan called out.
152153
- **v0.5.339** — Tier 2.3 + 2.2 mass extraction: completes the lower_expr split (~all extractable arms shipped) and finishes the lower_call.rs follow-up that v0.5.334's `ui_styling` extraction left open. Three rounds in one PR. **Round 1 — Member + Assign + New** (3 new sub-modules, 1110 LOC moved out of lower_expr): `lower/expr_member.rs` (424 LOC) handles `obj.prop` / `obj["k"]` / `obj[i]` / namespace forms (`Math.PI`) / enum member access / private field reads / `Symbol.iterator` fast path. `lower/expr_assign.rs` (330 LOC) handles `=` / compound assigns / property assigns / index assigns / destructuring assigns; depends on `lower_expr_assignment` (now `pub(super) fn`) and the `destructuring::lower_destructuring_assignment` helper. `lower/expr_new.rs` (414 LOC) routes `new C()` calls to user-defined classes, built-in JS classes (Date / Map / Set / RegExp / Buffer / TypedArray*), and dynamic `new (someFn)()` form. **Round 2 — `lower_call.rs` `lower_builtin_new`** (1 new sub-module, 399 LOC moved): `lower_call/builtin.rs` handles `new C()` codegen for built-in classes (Date / Map / Set / Buffer / fetch Headers/Request/Response / mongodb MongoClient / redis Redis / fastify App / ws WebSocketServer / pg Client/Pool / perry/plugin Decimal / AsyncLocalStorage / AbortController / Command). Calling-side promoted to `pub(super) fn`; the parent module imports via the existing `mod builtin` pattern. (`lower_native_method_call` 805 LOC was assessed but skipped — its 20+ helper cross-references make safe extraction much riskier than the leverage warrants for a single PR; deferred to a focused follow-up.) **Round 3 — `lower_expr` Call arm** (1 new sub-module, 3986 LOC moved): `lower/expr_call.rs` is the giant call dispatcher — by far the largest single arm in the codebase, handling Math.* / JSON.* / fetch / native module method dispatch / class static methods / Symbol / Reflect / Proxy / built-in coercions / etc. Required bumping 6 helpers in lower.rs (`extract_typed_parse_source_order`, `resolve_typed_parse_ty`, `try_desugar_reactive_text`, `try_desugar_reactive_animate`, `is_widget_modifier_name`, `is_generator_call_expr`) from private `fn` to `pub(super) fn` so the new sub-module can reach them. **Cumulative `lower_expr` reduction**: 6687 LOC (original) → 624 LOC (~91% reduction). The function is now a thin dispatcher that delegates almost every arm to a focused sub-module. **Cumulative `lower.rs` reduction**: 13591 LOC → 7554 LOC (~44% reduction). **Cumulative `lower_call.rs` reduction across the session**: 7000+ LOC → 5085 LOC (~27% reduction since v0.5.328, combining Tier 1.3 dispatch tables + ui_styling + builtin extractions). **What remains**: `lower_native_method_call` in lower_call.rs (805 LOC, assessed risky in this round), per-target codegen orchestrators in compile.rs (~1200 LOC), `resolve_import` family (~600 LOC), `compute_object_cache_key` + `ObjectCache` (~700 LOC), `build_optimized_libs` + `build_link_command` (~2000 LOC). Each is independently extractable; doing them as focused PRs preserves reviewability. **Verified**: cargo build --release clean; cargo test --workspace 434/0 = baseline; gap tests 25/28 = baseline; doc-tests --skip-xcompile 80/82 = baseline; comprehensive smoke compile exercising Math.*, JSON.parse → array methods chain (`data.map(x => x*2).reduce(...)`), String methods, Object.{keys,values}, console.* with multiple args, class instantiation + chained calls inside .forEach, function call with rest spread (`sum3(...args)`) — all match `node --experimental-strip-types` byte-for-byte. **Cumulative across this session** (v0.5.329-v0.5.339, eleven commits): all 13 plan items shipped as full or partial extractions; lower_expr is now ~91% smaller; the giant arm split is the largest cognitive-load reduction in the entire session.
153154
- **v0.5.338** — Tier 2.3 follow-up: extracts three more `lower_expr` arms from `crates/perry-hir/src/lower.rs` into focused sub-modules. (1) **`expr_function.rs`** (335 LOC) — both `ast::Expr::Arrow` (178 LOC) and `ast::Expr::Fn` (138 LOC) plus a shared `compute_closure_captures` helper that the original arms duplicated verbatim. The Arrow + Fn lowering shares almost all of its logic (parameter destructuring, body lowering with JS function-hoisting, closure capture analysis); the only differences are arrows capture `this` from the enclosing scope while function expressions don't, and arrows allow a single-expression body shorthand. Co-locating them lets the capture analysis become a real shared function instead of being copy-pasted. (2) **`expr_object.rs`** (508 LOC) — the `ast::Expr::Object` arm including its inline `is_closed_shape` predicate. This is the largest single arm extracted so far. The lowered shape depends on whether the literal is a "closed shape" (no spreads, all fixed string keys) — such literals lower to `new __AnonShape_N()` so downstream property access hits the codegen direct-GEP fast path; open-shape literals (spreads, computed keys, getters/setters) fall through to a generic `Object` / `ObjectSpread` HIR node. **Files**: 2 new sub-modules under `lower/`, plus the v0.5.337 `expr_misc.rs`. **lower_expr delta**: 6508 → 5716 LOC (-792 in this commit; 6687 → 5716 cumulative across v0.5.337+v0.5.338 = -971, ~14.5% total reduction). **Unblocked refactors enabled**: the shared `compute_closure_captures` helper is now a clean target for the Tier 4 follow-up that fuses outer `collect_local_refs_stmt` + `collect_assigned_locals_stmt` into one walk (currently runs both separately on the body). **What remains in Tier 2.3**: the biggest arms — `Call` (3986 LOC, by far the largest), `Member` (405), `New` (393), `Assign` (312). Each has its own helper-fn cross-references that need careful coordination; doing them in a single PR would balloon the diff to >5k LOC. **Verified**: cargo build --release clean; cargo test --workspace 434/0 = baseline; gap tests 25/28 = baseline; doc-tests --skip-xcompile 80/82 = baseline; smoke compile exercising arrow-with-capture, function expression, closed-shape object, spread object, computed key, and array-of-objects (`[1,2,3].map(n => ({ id: n, sq: n*n }))`) all match Node byte-for-byte. **Cumulative across this session** (v0.5.329-v0.5.338, ten commits): all plan items have shipped work; Tier 2.3 has now had two rounds of extractions and the pattern is well-established for the remaining bigger arms.
154155
- **v0.5.337** — Tier 2.3 of the compiler-improvement plan (pilot scope): begins splitting the 6,687-line `lower::lower_expr` function in `crates/perry-hir/src/lower.rs` by extracting 8 self-contained AST variants — `Cond`, `Await`, `SuperProp`, `Update`, `Tpl`, `Seq`, `MetaProp`, `Yield` — into a new `lower/expr_misc.rs` sub-module. Each becomes a free `pub(super) fn lower_<variant>(ctx: &mut LoweringContext, node: &ast::<Type>) -> Result<Expr>` taking the SWC AST node and returning the same `Result<Expr>` the original arm produced. Recursion goes through `super::lower_expr`, matching the pattern from Tier 2.1 (`compile.rs` split) and Tier 2.2 (`ui_styling` extracted from `lower_call.rs`). The match arms in `lower_expr` collapse to one-line delegations like `ast::Expr::Cond(cond) => expr_misc::lower_cond(ctx, cond)`. **Pilot rationale**: the extracted 8 are the smallest, well-bounded variants — each between 4 and 64 LOC, none introducing nested helper fns of its own (the original `Update` arm's nested-`match` shape ports cleanly), all using only public methods on `LoweringContext`. The bigger arms (`Call` 3986 LOC, `Object` 479, `Member` 405, `New` 393, `Assign` 312, `Arrow` 178) are followups: each carries cross-references and helper fns that need careful coordination, and a single PR splitting all 32 arms would balloon the diff to >10k LOC. The pilot proves the extraction pattern works without the recursion-vs-borrow-checker wrestling that giant-arm extraction sometimes produces. **Files**: new `crates/perry-hir/src/lower/expr_misc.rs` (222 LOC = 8 helpers + module doc + imports). lower.rs delta: 13599 → 13415 LOC overall (-184); the lower_expr function specifically went 6687 → 6508 LOC (-179, ~2.7%). Net workspace LOC roughly unchanged (extracted code still exists, just in a focused module). The win is cognitive load: each extracted helper is now individually testable, future variant work (e.g. the `Update` arm's PrivateName/Computed branches) doesn't have to scroll past the 6000-line `lower_expr` body. **What's NOT done in the pilot**: the 5 biggest arms remain inline. Each is independently extractable using the same pattern; doing them later as focused PRs avoids one massive diff. **Verified**: `cargo build --release` clean; `cargo test --workspace` 434/0 = baseline; gap tests 25/28 = baseline; doc-tests --skip-xcompile 80/82 = baseline; smoke compile of a TypeScript program exercising all 5 testable extracted variants (`cond`, `update`, `tpl`, `seq`, `yield` — Await/SuperProp/MetaProp don't have easy single-line repros) matches Node byte-for-byte. **Cumulative across this session** (v0.5.329-v0.5.337, nine commits): all 13 plan items shipped including the highest-risk lower_expr split (pilot scope). Tier 2.3 broader rollout is the only remaining followup; everything else from the plan is complete.

Cargo.lock

Lines changed: 28 additions & 28 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

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

113113
[workspace.package]
114-
version = "0.5.339"
114+
version = "0.5.340"
115115
edition = "2021"
116116
license = "MIT"
117117
repository = "https://github.com/PerryTS/perry"

0 commit comments

Comments
 (0)