You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CLAUDE.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
8
8
9
9
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.
10
10
11
-
**Current Version:** 0.5.338
11
+
**Current Version:** 0.5.339
12
12
13
13
## TypeScript Parity Status
14
14
@@ -149,6 +149,7 @@ First-resolved directory cached in `compile_package_dirs`; subsequent imports re
149
149
150
150
Keep entries to 1-2 lines max. Full details in CHANGELOG.md.
151
151
152
+
- **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.
152
153
- **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.
153
154
- **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.
154
155
- **v0.5.336** — Tier 4 follow-up: completes the remaining three perf items the plan called out (4.3, 4.4, 4.6), now matching the four already shipped in v0.5.335. **4.6 Arc<I18nTable>**: pre-fix `crates/perry/src/commands/compile.rs` cloned the per-module `i18n_snapshot` tuple inside the `par_iter()` codegen loop — every clone duplicated the (potentially large) `Vec<String>` of every translated string × every locale. New `pub i18n_table: Option<std::sync::Arc<(Vec<String>, usize, usize, Vec<String>, usize)>>` (was the bare tuple) on `CompileOptions`; `i18n_snapshot` is wrapped once at the top of the loop, the per-module clone is now a cheap Arc reference bump. The destructure at `crates/perry-codegen/src/codegen.rs::compile_module` was updated to `arc.as_ref()` deref. The cache-key derivation in `compute_object_cache_key` likewise now derefs through the Arc. Inner `I18nLowerCtx.translations` (codegen-side per-module copy) is still a Vec — wrapping it in Arc too would eliminate the second per-module clone but is a wider refactor tracked as a follow-up. Per-module saving: roughly 1 × `Vec<String>` clone per module per build (was 2). On a project with 30 modules and 1000 translated strings, this saves ~30 redundant Vec allocations + their String contents per compile. **4.4 parallel `.ll` write**: `compile.rs` post-codegen used to `for result in compile_results { fs::write(...) }` — sequential I/O that bottlenecked when codegen finished producing bytes faster than a single thread could drain. Refactored to: (a) sequential partition into `to_write: Vec<(PathBuf, Vec<u8>)>` + error reporting (errors print in source order, preserved from pre-fix), (b) parallel write via `to_write.par_iter().map(|(p, b)| fs::write(p, b)).collect()` — the OS handles concurrent writes to distinct paths fine, (c) bail on first I/O error after the par_iter finishes (preserves the "fail fast on disk-full / permission" semantics), (d) sequential print + `obj_paths` collection (so output is grouped not interleaved). Wall-time saving scales with module count and disk-writev parallelism (~2-4x faster on a SSD with 50+ modules, less on slow storage). **4.3 fuse mutable-captures passes**: `crates/perry-hir/src/lower.rs::widen_mutable_captures_stmts` had three back-to-back `for stmt in stmts.iter()` loops, each populating a separate HashSet (`scope_mutable`, `scope_captured`, `scope_assigned_at_level`). Fused into a single iteration that calls all three `collect_*` helpers per statement. The collectors read disjoint Expr/Stmt fields with no ordering dependency, so the union is identical. Saves 2 full Stmt slice traversals per scope; this pass runs over `module.init` + every function body + every class method/getter/setter/static_method/ctor body, so the savings compound on a large project. The mutating pass at the bottom (`widen_mutable_captures_stmt`) still runs separately because it depends on the union of all three sets. **Tier 4 complete**: all six items shipped (4.1 + 4.2 + 4.5 in v0.5.335; 4.3 + 4.4 + 4.6 here). **Verified**: cargo build --release clean; cargo test --workspace 434/0 = baseline; gap tests 25/28 = baseline; doc-tests --skip-xcompile 80/82 = baseline; multi-module #212 closure-capture smoke compile matches Node byte-for-byte (exercises widen_mutable_captures and the parallel codegen + write path). **Cumulative across this session** (v0.5.329-v0.5.336, eight commits): all 11 highest-leverage items in the compiler-improvement plan now shipped except Tier 2.3 (lower_expr split — biggest risk, deliberately left for a focused PR).
0 commit comments