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
refactor(compile): extract build_and_run_link into compile/link.rs (v0.5.342)
Tier 2.1 final extraction — moves the per-platform link command construction
(~1240 LOC) out of run_with_parse_cache into a new compile/link.rs sub-module.
The new pub(super) fn build_and_run_link takes 10 params (no struct needed —
is_* flags get re-derived from target internally) and owns:
- per-platform Command::new(...) selection (clang / swiftc / lld-link /
link.exe / Android NDK clang / ld64.lld for cross paths) plus SDK +
sysroot + triple discovery via xcrun
- entry-object _main rename via rust-objcopy for watchOS / visionOS Swift
shells and iOS / tvOS / watchOS game-loop variants
- object file accumulation, dead-strip flags
- jsruntime / runtime / stdlib link order with platform-specific
duplicate-symbol handling (Mach-O first-wins vs ELF
--allow-multiple-definition vs MSVC /FORCE:MULTIPLE)
- UI lib link + strip-dedup + GTK4 --whole-archive
- geisterhand lib link + Windows /INCLUDE:
- external perry.nativeLibrary crates: cargo build, framework / lib /
pkg-config additions, swift_sources compilation with dedup
- the final cmd.status()? + bail on non-zero
The dylib link path stays inline in compile.rs (returns early with a
CompileResult). Per-platform .app bundling and Android .so companion-lib
copying also stay inline since they happen post-link.
The is_cross_* helpers (is_cross_windows, is_cross_ios, is_cross_visionos,
is_cross_macos, is_cross_tvos) were only used inside the extracted block;
their declarations have been removed from compile.rs since link.rs
re-derives them.
compile.rs delta: 5019 → 3783 LOC (-1236, ~25% reduction).
Cumulative across v0.5.329-v0.5.342 (fourteen commits):
compile.rs 9391 → 3783 LOC (~60% total reduction).
Verified:
- cargo build --release: clean
- cargo test --workspace --exclude perry-ui-{ios,windows,visionos,
tvos,gtk4,android}: 434 passed, 0 failed, 5 ignored
- /tmp/run_gap_tests.sh: 25/28 (3 pre-existing categorical failures)
- doc-tests --skip-xcompile: 80/82 (1 pre-existing screenshot baseline)
- UI smoke compile (App + VStack + Text + Button) produces 856K macOS
binary, exits 0 in PERRY_UI_TEST_MODE=1, strip-dedup output (419 → 35
trimmed objects) matches v0.5.341 byte-for-byte
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.341
11
+
**Current Version:** 0.5.342
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.342** — Tier 2.1 final extraction: moves the per-platform link command construction out of `crates/perry/src/commands/compile.rs::run_with_parse_cache` into a new `compile/link.rs` sub-module. Pre-extraction this was a single ~1240-LOC inline block fanning out across macOS / iOS / tvOS / visionOS / watchOS / Android / Linux / Windows + 5 cross-compile permutations — every platform-specific link flag change churned the orchestrator file. The new `pub(super) fn build_and_run_link(args_input, ctx, target, obj_paths, compiled_features, runtime_lib, stdlib_lib, jsruntime_lib, exe_path, format) -> Result<()>` takes 10 params (no struct needed once `is_*` flags get re-derived from `target` internally) and owns: (1) per-platform `Command::new(...)` selection — clang on macOS/iOS, swiftc on watchOS/visionOS, Android NDK clang, lld-link / link.exe on Windows, ld64.lld on Linux→Apple cross paths, plus the matching SDK / sysroot / triple discovery via `xcrun --sdk ... --show-sdk-path` / `--find clang` / `--find swiftc`. (2) entry-object `_main` rename via `rust-objcopy --redefine-sym` for watchOS / visionOS Swift-app shells (`_main → _perry_main_init`) and iOS / tvOS / watchOS game-loop variants (`_main → __perry_user_main`). (3) accumulating object files + dead-strip flags (`-Wl,--gc-sections` / `-dead_strip` / `-Xlinker -dead_strip` / `/OPT:REF /OPT:ICF`). (4) library link order — jsruntime → runtime fallback → stdlib precedence rules (the comment explains the Mach-O first-wins vs ELF `--allow-multiple-definition` vs MSVC `/FORCE:MULTIPLE` differences). (5) `-o exe_path` / `/OUT:...`. (6) plugin-host `-Wl,-u,_<sym>` force-keep + `-rdynamic` on Linux. (7) per-platform framework / system-lib enumeration (UIKit / SwiftUI / AVFoundation on Apple, GTK4 via `pkg-config --libs gtk4` with hardcoded fallback on Linux, Win32 system libs on Windows, Android stub for `JNI_GetCreatedJavaVMs`). (8) UI lib + strip-dedup invocation + GTK4 `--whole-archive` for `js_stdlib_process_pending`. (9) geisterhand lib link + Windows `/INCLUDE:` symbol force-references. (10) external `perry.nativeLibrary` manifest crates: `cargo build --release [+nightly] [-Zbuild-std] --manifest-path` per crate, find the resulting staticlib, framework / lib / pkg-config additions, swiftc compilation of manifest-declared `swift_sources` for `--features watchos-swift-app` (with deduplication via `seen_swift_sources: HashSet<PathBuf>`), and metal-source target validation. (11) `cmd.status()?` invocation + bail on non-zero. **Visibility changes**: 0 — every helper called by the new module is already `pub(super)` from the prior 9 sub-module extractions in this Tier 2.1 stack. The new module accesses `find_geisterhand_*`, `find_ui_library`, `find_lld_link`, `find_msvc_link_exe`, `find_perry_windows_sdk`, `windows_pe_subsystem_flag`, `find_msvc_lib_paths`, `find_stdlib_library`, `find_llvm_tool`, `find_visionos_swift_runtime`, `find_watchos_swift_runtime`, `apple_sdk_version`, `strip_duplicate_objects_from_lib`, `build_geisterhand_libs`, and `rust_target_triple` (compile.rs's own private fn — accessible to children) via `super::*`. **Argument-passing approach**: the call site is in `run_with_parse_cache` AFTER `args.output` has already been consumed by `unwrap_or_else` (line 2429 in old numbering), so the function takes `args_input: &Path` (i.e. `&args.input`) rather than `&CompileArgs`. The other ~10 params are by reference — `&CompilationContext`, `Option<&str>`, `&[PathBuf]`, `&[String]`, `&Path` (×2), `&Option<PathBuf>` (×2), and `OutputFormat` by value (Copy). The dylib link path stays inline in `run_with_parse_cache` because it returns early with a `CompileResult`; per-platform `.app` bundling (iOS / visionOS / watchOS / tvOS) and Android `.so` companion-lib copying also stay inline since they happen after the link returns and depend on many post-link variables (`result_bundle_id`, `result_app_dir`, etc.). **`is_cross_*` cleanup**: `is_cross_windows`, `is_cross_ios`, `is_cross_visionos`, `is_cross_macos`, and `is_cross_tvos` were all only used inside the extracted block. Their declarations have been removed from `compile.rs`; the new `link.rs` re-derives them internally from `target`. **compile.rs delta**: 5019 → 3783 LOC (-1236, ~25% reduction). **Cumulative across this session** (v0.5.329-v0.5.342, fourteen commits): **compile.rs 9391 → 3783 LOC (~60% total reduction)**, lower_call.rs 7000+ → 4681 LOC (~33%), lower.rs 13591 → 7554 LOC (~44%), `lower::lower_expr` 6687 → 624 LOC (~91%). compile.rs is now ~40% of its starting size; what remains is the orchestrator entry points (`run`, `run_with_parse_cache`), context construction (`CompilationContext::new`), and the parse / typecheck / lower / codegen / cache pipeline — i.e. exactly what an orchestrator file should hold. The full `compile/` sub-module structure has 10 focused files: parse_cache (294 LOC), strip_dedup (518), library_search (704), targets (824), object_cache (747), resolve (849), optimized_libs (413), collect_modules (412), and now link (1245). **What still works**: cargo build --release clean; cargo test --workspace 434/0/5 = baseline; gap tests 25/28 = baseline (the 3 failing — `array_methods`, `console_methods`, `typed_arrays` — pre-existing categorical/CI gaps, not regression-induced); doc-tests --skip-xcompile 80/82 = baseline (the lone fail is the pre-existing `ui/gallery.ts` retina screenshot drift); UI smoke compile (`App({title, body: VStack([Text("OK"), Button("Click", () => {})]) })`) produces a 856K macOS binary that exits 0 in `PERRY_UI_TEST_MODE=1`, and the strip-dedup output (419 → 35 trimmed objects via 178 by-symbol-subset + 222 by-name-pattern + 16 rlib-extracted + 19 kept) matches v0.5.341 byte-for-byte — proves the live path through library_search + targets + strip_dedup + ObjectCache + resolve + link all works end-to-end. **Cumulative across this session** (v0.5.329-v0.5.342, fourteen commits): every plan item delivered, plus four follow-up rounds extracting roughly 13,200 LOC across 18 new sub-modules into the largest cognitive-load reduction in the project's history. Tier 2.1 is now complete.
152
153
- **v0.5.341** — Tier 2.1 final round: extracts the last two big bounded chunks of compile.rs. (1) **`compile/optimized_libs.rs`** (~390 LOC moved): the auto-rebuild driver that picks the smallest matching Cargo feature set for the user's TS code. Contains the `OptimizedLibs` struct + impl + `build_optimized_libs` fn (the cargo + workspace + target-dir hash-keyed driver) + the doc explaining the panic-mode + feature-derivation logic. Both runtime and stdlib halves fall back to the prebuilt libraries gracefully on failure. The hash-keyed `target/perry-auto-{:016x}` dir means consecutive runs with the same profile are no-ops after the first build. (2) **`compile/collect_modules.rs`** (~390 LOC moved): the transitive import-walk that builds `CompilationContext.native_modules` / `js_modules`. Walks the import graph from the entry file, lowers every TypeScript module to HIR, classifies each as native-compiled vs JS-runtime-loaded, and runs per-module HIR passes (`inline_functions`, `transform_generators`) before adding to context. Source hashes feed the V2.2 codegen cache key derivation. **Cumulative deltas this commit**: compile.rs 5795 → 5019 LOC (-776, ~13%). **Cumulative across the entire session** (v0.5.329-v0.5.341): **compile.rs 9391 → 5019 LOC (~47% total 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%). compile.rs is now nearly half its starting size; what remains is the `run_with_parse_cache` orchestrator + `CompilationContext::new` + `build_link_command` (the bulk — the inline link command construction that fans out per-platform). Further splits would be possible but each would require careful coordination of the 30+ helper variables threaded through `build_link_command`. **What still works**: 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 matches Node byte-for-byte. **The final compile/ sub-module structure** has 9 focused files: parse_cache (294 LOC), strip_dedup (518), library_search (704), targets (824), object_cache (747), resolve (849), optimized_libs (413), collect_modules (412). **Cumulative across this session** (v0.5.329-v0.5.341, thirteen commits): every plan item delivered, plus four follow-up rounds extracting roughly 12,000 LOC across 17 new sub-modules into the largest cognitive-load reduction in the project's history.
153
154
- **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.
154
155
- **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.
0 commit comments