Skip to content

Improve Zod compatibility support#5874

Open
TheHypnoo wants to merge 130 commits into
mainfrom
feat/zod-compat-fixtures
Open

Improve Zod compatibility support#5874
TheHypnoo wants to merge 130 commits into
mainfrom
feat/zod-compat-fixtures

Conversation

@TheHypnoo

@TheHypnoo TheHypnoo commented Jul 2, 2026

Copy link
Copy Markdown
Member

Summary

  • Add broader Zod 3 compatibility coverage and reduced regressions for dynamic imports, namespace exports, dynamic Function probes, instanceof behavior, and Object.prototype method fallback.
  • Improve general JS/TS runtime and codegen behavior required by Zod 3 and Zod 4 without adding package-specific Zod hooks.
  • Keep Zod 4 validation local-only; no Zod 4 fixture, lockfile, or node_modules are added to the repository.

Behavior change

  • new Function("<string>") now throws by default in every build: an ahead-of-time compiled binary cannot compile a code string built from runtime data. Feature-detecting libraries fall back cleanly to their non-Function path. Opt out with PERRY_EVAL_CSP=0.

Validation

  • cargo check -p perry-codegen -p perry
  • cargo build -p perry
  • ./tests/release/packages/_harness.sh --filter zod3-basic
  • Zod 4.4.3 local-only parity suite: 14/14 matrices pass against Node in --no-auto-optimize and optimized compile modes
  • Reduced regression tests for dynamic import package registry, namespace homonymous exports, dynamic Function fallback, arrays vs Error subclass instanceof, and Object.prototype method-call fallback

Summary by CodeRabbit

  • New Features
    • Improved namespace import/export handling (including nested namespaces) and more accurate dynamic import resolution.
    • Enhanced new.target behavior for imported constructors and improved Map/Set subclass initialization.
    • Better Object.prototype method fallback behavior across built-in types.
  • Bug Fixes
    • Fixed runtime edge cases for instanceof, Object.create, Object.defineProperty, Error.errors, Symbol.toStringTag, URL/File/Blob, string indexing, numeric dispatch, and BigInt equality in Map/Set.
    • Improved JSON.stringify replacer iteration/key ordering and prototype-walk safety.
  • Tests
    • Added/expanded integration tests and release fixtures (including Zod v3) to cover the above scenarios.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds namespace and constructor metadata plumbing through codegen and the compile pipeline, updates HIR lowering for re-exports and static initialization, revises runtime object-model and reflection behavior, and adds a broad regression suite plus a zod3-basic release fixture.

Changes

Codegen and HIR plumbing

Layer / File(s) Summary
Context fields and wiring
crates/perry-codegen/src/codegen/opts.rs, crates/perry-codegen/src/codegen/{mod,closure,entry,function,method}.rs, crates/perry-codegen/src/expr/mod.rs
Adds namespace-member maps/sets and constructor new.target flags, wired into lowering contexts and imported constructor metadata.
Namespace globals and member lowering
crates/perry-codegen/src/codegen/artifacts.rs, crates/perry-codegen/src/codegen/{helpers,module_globals_emit}.rs, crates/perry-codegen/src/expr/{dyn_extern_i18n,property_get}.rs, crates/perry-codegen/src/lower_call/{namespace_call,console_promise}.rs, crates/perry-codegen/src/lower_call/property_get/number_string.rs
Emits namespace globals/getters and resolves namespace-backed values, nested members, and string-method dispatch using the new maps.
Compile pipeline and cache wiring
crates/perry/src/commands/compile/{run_pipeline,object_cache,collect_modules}.rs, crates/perry-codegen/tests/*.rs
Computes and hashes the new namespace and constructor metadata, expands dynamic-import target discovery, and updates fixture defaults.
Re-exports, enum runtime, and static init
crates/perry-hir/src/lower/module_decl*.rs, crates/perry-hir/src/lower_decl/{enum_decl,static_init,mod}.rs, crates/perry-hir/src/lower/{stmt,lower_module_fn,lower_expr/arm_class,closure_analysis}.rs, crates/perry-hir/src/dynamic_import*.rs
Adds imported-binding re-export detection, runtime enum emission, closure-body dynamic-import traversal, and new static-init/preallocation helpers.

Constructor and class lowering

Layer / File(s) Summary
new.target helpers
crates/perry-codegen/src/lower_call/new_helpers.rs
Adds constructor arity, new.target chain detection, Map/Set default super kind detection, and new.target save/restore helpers.
Constructor call emission
crates/perry-codegen/src/lower_call/new.rs, crates/perry-codegen/src/codegen/method.rs
Wraps imported constructor calls with new.target save/restore and emits Map/Set subclass init in constructor paths.
Runtime class linkage
crates/perry-runtime/src/object/class_registry/{construct,state,parent_static}.rs
Computes dynamic parent prototypes, links constructor fields for Error-derived classes, and adds refresh_class_decl_prototype_parent.
Field initializers and class shadowing
crates/perry-codegen/src/expr/this_super_call.rs, crates/perry-hir/src/lower/{expr_new,expr_function}.rs, crates/perry-hir/src/lower_decl/{block,body_stmt}.rs
Runs field initializers after built-in Error super() calls, fixes new () shadow resolution, and pre-registers class locals.
Forward-captured let preallocation
crates/perry-hir/src/lower/{closure_analysis,lower_module_fn}.rs
Preallocates boxes for lets captured before declaration across module init, functions, and class member bodies.
Eval CSP probe and literal repair
crates/perry-hir/src/{eval_classifier,lower/const_fold_fn}.rs, crates/perry-hir/src/lower_patterns.rs
Changes the CSP probe default and routes regex/string repair through a shared helper.

Runtime object-model, reflection, and value fixes

Layer / File(s) Summary
instanceof and brand checks
crates/perry-runtime/src/object/instanceof.rs, crates/perry-runtime/src/object/global_this/proto_methods.rs
Adds prototype identity checks, ordinary_function_has_instance, and fetch/Blob/File/URL instanceof mapping.
Descriptors and own-property checks
crates/perry-runtime/src/object/{descriptors,object_ops/has_own}.rs, crates/perry-runtime/src/object/global_this.rs
Excludes inherited Object.prototype shim keys from descriptors, own-property, and enumerable checks; refines class-owned static field descriptor attrs.
Prototype resolution and property definition
crates/perry-runtime/src/object/object_ops/{prototype,define_properties,define_property}.rs
Adds early static-prototype fast paths, null/undefined cycle guards, and class-ref defineProperty support.
Field get and has-property dispatch
crates/perry-runtime/src/object/field_get_set/{get_field_by_name,get_field_by_name_tail,has_property}.rs
Adds string-receiver fast paths, constructor/URL resolution, recursion limiting, and prototype-based fallbacks.
Object.prototype fallback dispatch
crates/perry-runtime/src/object/native_call_method*.rs, crates/perry-runtime/src/set.rs, crates/perry-runtime/src/map.rs
Adds inherited Object method dispatch fallback and BigInt-aware equality/hashing in Set/Map.
URL and fetch reflection
crates/perry-runtime/src/object/{to_string_tag,mod,symbol/get}.rs, crates/perry-runtime/src/url/{mod,parse}.rs, crates/perry-stdlib/src/fetch/dispatch.rs, crates/perry-codegen/src/expr/instance_misc1.rs
Adds fetch/URL toStringTag support, prototype linkage, reserved class ids, and Symbol.toStringTag resolution for fetch/URL handles.
String index and array descriptor fixes
crates/perry-runtime/src/string/char_ops.rs, crates/perry-runtime/src/value/dyn_index.rs, crates/perry-codegen/src/expr/{index_get,property_get}.rs, crates/perry-runtime/src/object/array_object_ops.rs, crates/perry-runtime/src/proxy.rs
Rewrites string indexed-read for canonical numeric semantics and adds array_has_descriptors.
JSON replacer and iterable detection
crates/perry-runtime/src/json/replacer.rs, crates/perry-runtime/src/collection_iter.rs
Fixes replacer ordering/field reads and iterable pointer extraction.
Dynamic arithmetic and formatting
crates/perry-runtime/src/value/dynamic_arith.rs, crates/perry-codegen/src/expr/binary.rs, crates/perry-runtime/src/string/format.rs
Adds dynamic_number_operand coercion, non-numeric binary dispatch, and short-exponent formatting helper.
String method dispatch gating
crates/perry-codegen/src/lower_call/property_get.rs
Narrows the string fast path and tightens Number/BigInt toString gating.

Integration tests and fixtures

Layer / File(s) Summary
zod3-basic release fixture
tests/release/packages/zod3-basic/*
Adds entry.ts, expected.txt, fixture.sh, and package.json for a large Zod v3 release fixture.
instanceof and reflection tests
tests/test_{array_not_instanceof_error_subclass,file_instanceof,function_prototype_instanceof,url_instanceof_reflection,builtin_prototype_and_fetch_reflection}.sh
Adds reflection and instanceof regression tests.
BigInt and arithmetic tests
tests/test_{bigint_samevalue_map,bigint_samevalue_set,dynamic_arithmetic_bigint_any}.sh
Adds BigInt SameValue and dynamic bigint arithmetic regression tests.
Constructor and class tests
tests/test_{class_define_property_name,dynamic_extends_class_binding,dynamic_parent_builtin_no_downgrade,error_subclass_arrow_field_init,imported_error_new_target,new_parameter_shadows_class,map_set_subclass_constructor_iterable,imported_static_field_alias_method,dynamic_class_extends_once}.sh
Adds constructor and class-lowering regression tests.
Namespace and module tests
tests/test_{dynamic_function_probe_fallback_stderr,dynamic_import_package_registry,imported_default_named_reexport,export_enum_runtime_object,namespace_*}.sh
Adds namespace and module import/export regression tests.
Object/prototype and misc runtime tests
tests/test_{forward_captured_let_box,object_create_*,reflect_set_frozen_array,own_to_string_method,json_stringify_replacer_key_order,string_computed_length,unicode_regex_literal,url_href_normalization,number_to_string_large_exponent,missing_error_property_undefined,object_prototype_method_call_fallback,prototype_cycle_has_property}.sh
Adds runtime object-model regression tests.

Estimated code review effort: 5 (Critical) | ~150 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning It covers summary and validation, but omits required Changes, Related issue, Test plan checklist, Screenshots/output, and Checklist sections. Add the missing template sections: Changes, Related issue, Test plan with commands/checklist, optional Screenshots/output, and the final Checklist.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and clearly matches the main change: improved Zod compatibility support.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/zod-compat-fixtures

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

TheHypnoo and others added 7 commits July 2, 2026 16:22
# Conflicts:
#	crates/perry-codegen/src/codegen/method.rs
#	crates/perry-hir/src/lower_decl/body_stmt.rs
#	crates/perry-runtime/src/object/class_registry/construct.rs
#	crates/perry-runtime/src/object/object_ops/define_properties.rs
#	crates/perry-runtime/src/value/dynamic_arith.rs
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{}

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (11)
crates/perry-codegen/tests/native_proof_buffer_views.rs (1)

10-64: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Add namespace_import_prefixes to empty_opts()

CompileOptions now requires namespace_import_prefixes, but this test helper still omits it. That makes crates/perry-codegen/tests/native_proof_buffer_views.rs fail to compile with E0063.

Fix
         namespace_member_namespace_prefixes: std::collections::HashMap::new(),
+        namespace_import_prefixes: std::collections::HashMap::new(),
         emit_ir_only: true,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-codegen/tests/native_proof_buffer_views.rs` around lines 10 -
64, The empty_opts() test helper is missing the new CompileOptions field
namespace_import_prefixes, which causes the struct literal to be incomplete.
Update empty_opts() to initialize namespace_import_prefixes alongside the other
namespace-related collections in CompileOptions so native_proof_buffer_views.rs
continues to compile.
crates/perry-codegen/tests/native_proof_regressions.rs (1)

11-65: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Add namespace_import_prefixes to empty_opts()
CompileOptions now requires namespace_import_prefixes, so this struct literal will not compile until that field is initialized (or the literal uses ..Default::default()).

🐛 Fix
         namespace_member_namespace_prefixes: std::collections::HashMap::new(),
+        namespace_import_prefixes: std::collections::HashMap::new(),
         emit_ir_only: true,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-codegen/tests/native_proof_regressions.rs` around lines 11 - 65,
The CompileOptions literal in empty_opts() is missing the new
namespace_import_prefixes field, so update this helper to initialize that field
consistently with the other namespace/import maps (or switch the struct literal
to use default initialization where appropriate). Keep the fix local to
empty_opts() and ensure the CompileOptions construction still covers all
required fields referenced elsewhere in native_proof_regressions.rs.
crates/perry-codegen/tests/shadow_slot_hygiene.rs (1)

5-59: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Add namespace_import_prefixes to empty_opts() CompileOptions now requires this field, so the fixture won't compile until it’s included.

🐛 Fix
         namespace_member_namespace_prefixes: std::collections::HashMap::new(),
+        namespace_import_prefixes: std::collections::HashMap::new(),
         emit_ir_only: true,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-codegen/tests/shadow_slot_hygiene.rs` around lines 5 - 59, The
empty_opts() test fixture is missing the new CompileOptions field
namespace_import_prefixes, so it no longer matches the struct definition. Update
empty_opts() in shadow_slot_hygiene.rs to initialize namespace_import_prefixes
alongside the other namespace_* collections, using the appropriate empty
collection type consistent with the CompileOptions fields.
crates/perry-codegen/tests/static_symbol_hygiene.rs (1)

5-59: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Add namespace_import_prefixes to empty_opts()CompileOptions requires namespace_import_prefixes: std::collections::HashMap::new(), so this test fixture will not compile without it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-codegen/tests/static_symbol_hygiene.rs` around lines 5 - 59, The
test fixture in empty_opts() is missing the CompileOptions field
namespace_import_prefixes, so the struct initialization is incomplete and won’t
compile. Update empty_opts() to include namespace_import_prefixes with an empty
std::collections::HashMap, keeping it consistent with the other namespace_*
option fields in the CompileOptions setup.
crates/perry-codegen/tests/typed_feedback.rs (1)

36-90: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Add namespace_import_prefixes to empty_opts()
This struct literal is missing the required namespace_import_prefixes field, so crates/perry-codegen/tests/typed_feedback.rs will not compile until it’s initialized here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-codegen/tests/typed_feedback.rs` around lines 36 - 90, The
CompileOptions literal in empty_opts is missing the required
namespace_import_prefixes field, so update that constructor to initialize it
alongside the other namespace_* maps and sets. Use the empty_opts helper in
typed_feedback.rs as the fix location and ensure the new field is set
consistently with the rest of the default CompileOptions values so the test file
compiles.
crates/perry-codegen/tests/argless_builtin_extra_args.rs (1)

10-64: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Add namespace_import_prefixes to empty_opts
CompileOptions now includes namespace_import_prefixes, but this struct literal still omits it and has no ..Default::default(), so the test file won't compile.

Suggested fix
         namespace_member_origin_names: std::collections::HashMap::new(),
         namespace_member_vars: std::collections::HashSet::new(),
         namespace_member_namespace_prefixes: std::collections::HashMap::new(),
+        namespace_import_prefixes: std::collections::HashMap::new(),
         emit_ir_only: true,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-codegen/tests/argless_builtin_extra_args.rs` around lines 10 -
64, The empty_opts helper is missing the new CompileOptions field
namespace_import_prefixes, so the struct literal no longer matches the type.
Update empty_opts to initialize namespace_import_prefixes alongside the other
namespace_* maps, using the same pattern as the nearby CompileOptions fields, so
the test helper stays in sync with CompileOptions.
crates/perry-codegen/tests/destructure_call_location.rs (1)

21-75: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Add namespace_import_prefixes to this CompileOptions literal. This field is required by CompileOptions, so this test file will fail to compile until it’s added here too.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-codegen/tests/destructure_call_location.rs` around lines 21 -
75, Add the missing namespace_import_prefixes field to the CompileOptions
literal in base_opts so the test stays in sync with the CompileOptions struct;
use the same pattern as the other HashMap/HashSet initializers in this helper
and update any related constructor-style test setup in
destructure_call_location.rs if needed.
crates/perry-codegen/tests/class_keys_gc_root.rs (1)

29-83: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Add namespace_import_prefixes to entry_opts()
This CompileOptions literal is missing the new namespace_import_prefixes field, so the test crate no longer matches CompileOptions. Add the same initializer used in the other helpers.

🐛 Suggested fix
         namespace_member_origin_names: std::collections::HashMap::new(),
         namespace_member_vars: std::collections::HashSet::new(),
         namespace_member_namespace_prefixes: std::collections::HashMap::new(),
+        namespace_import_prefixes: std::collections::HashMap::new(),
         emit_ir_only: true,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-codegen/tests/class_keys_gc_root.rs` around lines 29 - 83, The
CompileOptions initializer in entry_opts() is missing the new
namespace_import_prefixes field, so the test helper no longer matches the struct
definition. Update the entry_opts() literal to include namespace_import_prefixes
with the same initialization pattern used by the other CompileOptions helper
constructors in this test suite. Keep the field ordering consistent with the
surrounding CompileOptions setup so the test compiles cleanly.
crates/perry-codegen/tests/large_object_barriers.rs (1)

5-59: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Add namespace_import_prefixes to empty_opts() in crates/perry-codegen/tests/large_object_barriers.rs
CompileOptions includes this field, so the helper is incomplete until namespace_import_prefixes: std::collections::HashMap::new(), is added.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-codegen/tests/large_object_barriers.rs` around lines 5 - 59, The
CompileOptions builder in empty_opts() is missing the namespace_import_prefixes
field, so update that helper to initialize it alongside the other namespace_*
maps. Add namespace_import_prefixes with an empty HashMap in empty_opts() so the
test setup matches the current CompileOptions definition.
crates/perry-codegen/tests/macos_bundle_chdir_gate.rs (1)

11-65: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Add namespace_import_prefixes to this CompileOptions literal. This field is part of CompileOptions, so the helper is incomplete and won’t compile as written.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-codegen/tests/macos_bundle_chdir_gate.rs` around lines 11 - 65,
The `entry_opts` helper builds a `CompileOptions` literal but is missing the
`namespace_import_prefixes` field, so update that constructor to include it
alongside the other namespace-related maps/sets. Use the `CompileOptions` struct
in `entry_opts` as the anchor and add the missing field with the appropriate
empty collection value so the test compiles again.
crates/perry-codegen/tests/constructor_recursion.rs (1)

5-59: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Add namespace_import_prefixes to this CompileOptions literal. This initializer is missing a required field and breaks compilation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-codegen/tests/constructor_recursion.rs` around lines 5 - 59, The
empty_opts helper’s CompileOptions initializer is missing the
namespace_import_prefixes field, so update this CompileOptions literal in
empty_opts to include that required member alongside the other namespace-related
maps/sets. Use the existing CompileOptions struct definition and the surrounding
fields in empty_opts to add namespace_import_prefixes with the appropriate empty
default so the test compiles again.
🧹 Nitpick comments (7)
crates/perry-runtime/src/object/class_registry/state.rs (1)

510-551: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the duplicated parent-prototype resolution.

The parent_proto_bits computation in class_decl_prototype_value (Lines 510-522) and refresh_class_decl_prototype_parent (Lines 535-547) is identical. Extracting a shared helper keeps the two paths from drifting.

♻️ Proposed helper
fn resolve_class_parent_prototype_bits(class_id: u32) -> Option<u64> {
    dynamic_parent_prototype_bits(class_id).or_else(|| {
        get_parent_class_id(class_id)
            .filter(|parent_id| *parent_id != 0 && *parent_id != class_id)
            .and_then(|parent_id| {
                builtin_prototype_value_for_class_id(parent_id)
                    .or_else(|| {
                        let parent_proto = class_decl_prototype_value(parent_id);
                        ((parent_proto.to_bits() >> 48) == 0x7FFD).then_some(parent_proto)
                    })
                    .map(f64::to_bits)
            })
            .or_else(global_object_prototype_bits)
    })
}

Then both sites become let parent_proto_bits = resolve_class_parent_prototype_bits(class_id);.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-runtime/src/object/class_registry/state.rs` around lines 510 -
551, The parent-prototype resolution logic is duplicated in
class_decl_prototype_value and refresh_class_decl_prototype_parent, so extract
it into a shared helper such as resolve_class_parent_prototype_bits(class_id:
u32) -> Option<u64>. Move the existing
dynamic_parent_prototype_bits/get_parent_class_id/builtin_prototype_value_for_class_id/class_decl_prototype_value/global_object_prototype_bits
chain into that helper, then have both call sites use it to compute
parent_proto_bits before object_set_static_prototype.
crates/perry-runtime/src/object/native_call_method.rs (1)

1268-1289: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid per-call StringHeader allocation for the default toString/valueOf fallback.

This fallback resolves the default Object.prototype.toString/valueOf for objects with no own/vtable/prototype-sidetable method — likely the common case for plain objects/class instances. Each hit allocates a fresh StringHeader via js_string_from_bytes. Consider interning the three method-name StringHeaders once (e.g. thread-local or OnceLock) to avoid the allocation on this now-common path.

♻️ Suggested direction
-        if matches!(method_name, "toString" | "toLocaleString" | "valueOf") {
-            let method_key =
-                crate::string::js_string_from_bytes(method_name.as_ptr(), method_name.len() as u32);
+        if matches!(method_name, "toString" | "toLocaleString" | "valueOf") {
+            let method_key = interned_method_key(method_name); // cached StringHeader per name
             if !method_key.is_null() {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-runtime/src/object/native_call_method.rs` around lines 1268 -
1289, The default fallback in native_call_method for
toString/toLocaleString/valueOf is allocating a fresh StringHeader on every call
via js_string_from_bytes. Cache or intern these three method-name keys once (for
example with thread-local storage or OnceLock) and reuse them in the method_name
match path before calling js_object_get_field_by_name, so the common
Object.prototype fallback avoids per-call allocation.
crates/perry-runtime/src/object/field_get_set/get_field_by_name.rs (1)

30-57: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Consolidate string length handling
The new receiver fast path already covers string length/canonical-index reads, so the later SSO and heap-string length branches are duplicated for string receivers. constructor still needs the existing fallback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-runtime/src/object/field_get_set/get_field_by_name.rs` around
lines 30 - 57, The get_field_by_name fast path already handles string receivers
for canonical index and length lookups, so remove the duplicated SSO and
heap-string length branches later in the same lookup path. Keep the existing
fallback for constructor in place, and ensure the string-receiver handling
remains centralized in get_field_by_name using the existing string-pointer logic
and JSValue/string index helpers.
crates/perry-hir/src/lower_patterns.rs (1)

162-165: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Minor: no ASCII fast-path for regex pattern repair.

Unlike normalize_swc_string_literal (Line 174), which short-circuits on raw.is_ascii() || decoded.is_ascii() before calling repair_latin1_decoded_utf8, the regex path always runs the byte-reinterpretation loop even for pure-ASCII patterns (the overwhelming majority). Functionally harmless (round-trips to the same string) but adds needless work per regex literal.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-hir/src/lower_patterns.rs` around lines 162 - 165, The regex
literal lowering in ast::Lit::Regex currently always calls
repair_latin1_decoded_utf8 on re.exp.to_string(), unlike
normalize_swc_string_literal which skips the repair work for ASCII input. Update
the Expr::RegExp path to add the same ASCII fast-path so pure-ASCII regex
patterns return the original string without entering the byte-reinterpretation
loop, while still preserving the existing repair behavior for non-ASCII
patterns.
tests/test_reflect_set_frozen_array.sh (1)

6-7: 📐 Maintainability & Code Quality | 🔵 Trivial

Binary preference order flipped vs. sibling tests.

Other test scripts default to target/release/perry then fall back to debug; this one defaults to debug then falls back to release, and uses perry run instead of the compile+execute pattern used elsewhere (so it skips --no-auto-optimize/optimized-path coverage). Functionally harmless, just inconsistent with the rest of the suite.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_reflect_set_frozen_array.sh` around lines 6 - 7, The test script’s
binary selection and execution flow are inconsistent with the sibling tests: it
prefers the debug build first and uses a direct run path instead of the usual
compile-then-execute pattern. Update the setup in
test_reflect_set_frozen_array.sh to match the other test scripts by preferring
target/release/perry first and falling back to debug, and switch the invocation
to the same compile+execute style used elsewhere so it exercises the optimized
path and --no-auto-optimize coverage consistently.
crates/perry-codegen/src/codegen/opts.rs (1)

178-190: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Terse docs vs. surrounding convention.

Every neighboring field in this file (e.g. namespace_member_prefixes at Line 166-177, import_function_origin_names at Line 104-116) carries an "Issue #NNN" rationale explaining the specific bug it fixes and why the field is needed. The four new fields here have only a one-line description of what they store, not why (e.g. what breaks without namespace_member_origin_names vs. relying on namespace_member_prefixes alone). Given how easy it is to conflate these similarly-shaped maps, a short rationale would help future maintainers.

Also applies to: 583-590

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-codegen/src/codegen/opts.rs` around lines 178 - 190, The new
namespace-related option fields in opts.rs are missing the same “Issue `#NNN`”
rationale used by neighboring entries, so add concise docs explaining what
breaks without each of namespace_member_origin_names, namespace_member_vars,
namespace_member_namespace_prefixes, and namespace_import_prefixes. Update the
field comments near the Options struct and the related doc block around
namespace_member_prefixes to clarify why these separate maps/sets are needed
instead of relying on the existing namespace_member_prefixes behavior alone.
tests/test_dynamic_import_package_registry.sh (1)

20-33: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant compilePackages config.

compilePackages is declared both under perry.allow.compilePackages and directly under perry.compilePackages with identical values. If only one is the real schema key, consider dropping the other to avoid confusion for future readers of this fixture.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_dynamic_import_package_registry.sh` around lines 20 - 33, The
package fixture in the dynamic import registry test has duplicate
compilePackages settings with the same value, which is confusing. Update the
package.json fixture used in this test to keep only the correct schema location
for compilePackages and remove the redundant one, using the perry and
compilePackages keys in the test fixture as the reference points.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/perry-codegen/src/codegen/artifacts.rs`:
- Around line 1351-1371: The extern emission in artifacts::cross_module
namespace handling can declare the same `@__perry_ns_`<prefix> more than once if a
prefix is collected here and also later via dynamic_import_path_to_prefix. Add a
shared deduplication check before calling LlModule::add_external_global so both
loops reuse the same seen set or helper, and ensure the
namespace_extern_prefixes emission and the later dynamic import emission both
skip already-emitted prefixes.

In `@crates/perry-codegen/src/codegen/method.rs`:
- Around line 238-255: Add a traversal guard to map_set_default_super_kind to
match node_stream_parent_kind: the extends_name walk currently has no
cycle/depth limit and can loop forever on malformed or cyclic class hierarchies.
Update the loop to track depth (or another visited/limit check) and stop after
the same kind of bounded traversal used in node_stream_parent_kind, returning
None when the chain is too deep or unsafe to follow further.

In `@crates/perry-hir/src/dynamic_import.rs`:
- Around line 159-178: The Export::ReExport resolution in flatten_exports is
starting a fresh DFS each time, which loses the current visited state and can
recurse indefinitely on circular re-exports. Update the ReExport branch in
dynamic_import.rs to reuse the existing DFS state by calling flatten_into with
the current visited set instead of invoking flatten_exports, keeping the cycle
guard intact.

In `@crates/perry-hir/src/lower_decl/body_stmt.rs`:
- Around line 314-352: The fresh-class lowering in body_stmt.rs evaluates the
class extends expression twice: once through RegisterClassParentDynamic and
again when building ClassExprFresh via the "__perry_parent_value" initializer.
Update the lowering logic around bind_fresh_class_value and the
class.extends_expr handling so the extends expression is lowered once into a
temporary and that same Expr is reused for both the parent registration and the
fresh class initializer, preserving single evaluation and side-effect behavior.
- Around line 362-382: The `bind_fresh_class_value` branch in
`build_interleaved_static_init_stmts` is replaying `__perry_static_init_*`
blocks after all static fields, which breaks source-order behavior for `static
{}` blocks. Update the fresh-class path to use the same interleaved replay logic
as the non-fresh path by preserving the original ordering from
`class_decl.class.body`, while still using the fresh snapshot data for static
fields and methods. Refer to `build_interleaved_static_init_stmts`,
`bind_fresh_class_value`, and the `ClassExprFresh` handling to keep the fix
localized.

In `@crates/perry/src/commands/compile/object_cache.rs`:
- Around line 540-571: The object cache key computed in
compute_object_cache_key_with_env is missing the new namespace_import_prefixes
field, which can cause stale object reuse when only that map changes. Update the
hash serialization alongside the existing namespace_member_origin_names,
namespace_member_vars, and namespace_member_namespace_prefixes handling to
include opts.namespace_import_prefixes in a stable, deterministic order, using
the same pattern as the other namespace-related maps. Make sure the field is
also reflected consistently with empty_opts() and CompileOptions so the cache
key fully captures all namespace resolution inputs.

In `@crates/perry/src/commands/compile/run_pipeline.rs`:
- Around line 1760-1771: `normalize_namespace_path` is doing repeated uncached
`std::fs::canonicalize` calls in the hot per-module compile path, which is
causing redundant filesystem work. Update the `run_pipeline` setup so this
closure uses a shared cache for normalized paths, reusing previously computed
canonicalized results instead of re-hitting the filesystem on every invocation.
You can mirror the existing upfront native-module caching already done with
`path_to_module_name` and `module_name_to_path`, and wire the cache into all
call sites that use `normalize_namespace_path`, including the per-module loop
and `register_namespace_export_surface!`.
- Around line 104-154: The parent-class resolution in
class_chain_uses_new_target can pick the wrong class when multiple exports share
the same name. Update exported_class_by_name to accept and use the
class_canonical_path (instead of falling back to the first same-named export) so
it prefers the canonical module path consistently with the transitive closure
lookup, and then thread that value through class_chain_uses_new_target.
- Around line 2732-2890: Namespace re-export aliases are missing class/enum
dispatch metadata because the direct_namespace_source,
transitive_namespace_source, and Named re-export paths return immediately after
register_namespace_export_surface! and never populate imported_classes or
imported_enums. Update run_pipeline.rs by centralizing the namespace re-export
handling in the same helper/macro used for namespace aliases, and ensure all
re-export branches that call register_namespace_export_surface! also add the
class/enum metadata before continuing, matching the NamespaceReExport path’s
behavior.

In `@tests/test_builtin_prototype_and_fetch_reflection.sh`:
- Line 61: The oracle in tests/test_builtin_prototype_and_fetch_reflection.sh
currently uses node "$TMPDIR/main.ts", which depends on host Node supporting
direct TypeScript execution. Update the test to avoid this runtime dependency by
hardcoding the expected log like the sibling tests, or by using the same pinned
Node version/flags as the rest of the suite; keep the change focused in the test
script around the main.ts oracle setup.

In `@tests/test_unicode_regex_literal.sh`:
- Line 35: The oracle generation step in the test script unconditionally runs
node, so add a node availability guard before the expected output is produced.
Mirror the existing perry check pattern by checking for node, emitting SKIP, and
exiting 0 when it is missing; otherwise continue to run node "$TMPDIR/main.ts"
in the same test flow.

In `@tests/test_url_href_normalization.sh`:
- Line 31: The test script calls node in the expected-output setup without
checking availability, so it should be guarded like
test_unicode_regex_literal.sh. Update the test_url_href_normalization.sh flow
around the node invocation that builds expected.log to first verify node exists,
and if not, skip the test instead of failing. Use the existing shell test setup
and matching guard pattern from the other test to keep behavior consistent.

---

Outside diff comments:
In `@crates/perry-codegen/tests/argless_builtin_extra_args.rs`:
- Around line 10-64: The empty_opts helper is missing the new CompileOptions
field namespace_import_prefixes, so the struct literal no longer matches the
type. Update empty_opts to initialize namespace_import_prefixes alongside the
other namespace_* maps, using the same pattern as the nearby CompileOptions
fields, so the test helper stays in sync with CompileOptions.

In `@crates/perry-codegen/tests/class_keys_gc_root.rs`:
- Around line 29-83: The CompileOptions initializer in entry_opts() is missing
the new namespace_import_prefixes field, so the test helper no longer matches
the struct definition. Update the entry_opts() literal to include
namespace_import_prefixes with the same initialization pattern used by the other
CompileOptions helper constructors in this test suite. Keep the field ordering
consistent with the surrounding CompileOptions setup so the test compiles
cleanly.

In `@crates/perry-codegen/tests/constructor_recursion.rs`:
- Around line 5-59: The empty_opts helper’s CompileOptions initializer is
missing the namespace_import_prefixes field, so update this CompileOptions
literal in empty_opts to include that required member alongside the other
namespace-related maps/sets. Use the existing CompileOptions struct definition
and the surrounding fields in empty_opts to add namespace_import_prefixes with
the appropriate empty default so the test compiles again.

In `@crates/perry-codegen/tests/destructure_call_location.rs`:
- Around line 21-75: Add the missing namespace_import_prefixes field to the
CompileOptions literal in base_opts so the test stays in sync with the
CompileOptions struct; use the same pattern as the other HashMap/HashSet
initializers in this helper and update any related constructor-style test setup
in destructure_call_location.rs if needed.

In `@crates/perry-codegen/tests/large_object_barriers.rs`:
- Around line 5-59: The CompileOptions builder in empty_opts() is missing the
namespace_import_prefixes field, so update that helper to initialize it
alongside the other namespace_* maps. Add namespace_import_prefixes with an
empty HashMap in empty_opts() so the test setup matches the current
CompileOptions definition.

In `@crates/perry-codegen/tests/macos_bundle_chdir_gate.rs`:
- Around line 11-65: The `entry_opts` helper builds a `CompileOptions` literal
but is missing the `namespace_import_prefixes` field, so update that constructor
to include it alongside the other namespace-related maps/sets. Use the
`CompileOptions` struct in `entry_opts` as the anchor and add the missing field
with the appropriate empty collection value so the test compiles again.

In `@crates/perry-codegen/tests/native_proof_buffer_views.rs`:
- Around line 10-64: The empty_opts() test helper is missing the new
CompileOptions field namespace_import_prefixes, which causes the struct literal
to be incomplete. Update empty_opts() to initialize namespace_import_prefixes
alongside the other namespace-related collections in CompileOptions so
native_proof_buffer_views.rs continues to compile.

In `@crates/perry-codegen/tests/native_proof_regressions.rs`:
- Around line 11-65: The CompileOptions literal in empty_opts() is missing the
new namespace_import_prefixes field, so update this helper to initialize that
field consistently with the other namespace/import maps (or switch the struct
literal to use default initialization where appropriate). Keep the fix local to
empty_opts() and ensure the CompileOptions construction still covers all
required fields referenced elsewhere in native_proof_regressions.rs.

In `@crates/perry-codegen/tests/shadow_slot_hygiene.rs`:
- Around line 5-59: The empty_opts() test fixture is missing the new
CompileOptions field namespace_import_prefixes, so it no longer matches the
struct definition. Update empty_opts() in shadow_slot_hygiene.rs to initialize
namespace_import_prefixes alongside the other namespace_* collections, using the
appropriate empty collection type consistent with the CompileOptions fields.

In `@crates/perry-codegen/tests/static_symbol_hygiene.rs`:
- Around line 5-59: The test fixture in empty_opts() is missing the
CompileOptions field namespace_import_prefixes, so the struct initialization is
incomplete and won’t compile. Update empty_opts() to include
namespace_import_prefixes with an empty std::collections::HashMap, keeping it
consistent with the other namespace_* option fields in the CompileOptions setup.

In `@crates/perry-codegen/tests/typed_feedback.rs`:
- Around line 36-90: The CompileOptions literal in empty_opts is missing the
required namespace_import_prefixes field, so update that constructor to
initialize it alongside the other namespace_* maps and sets. Use the empty_opts
helper in typed_feedback.rs as the fix location and ensure the new field is set
consistently with the rest of the default CompileOptions values so the test file
compiles.

---

Nitpick comments:
In `@crates/perry-codegen/src/codegen/opts.rs`:
- Around line 178-190: The new namespace-related option fields in opts.rs are
missing the same “Issue `#NNN`” rationale used by neighboring entries, so add
concise docs explaining what breaks without each of
namespace_member_origin_names, namespace_member_vars,
namespace_member_namespace_prefixes, and namespace_import_prefixes. Update the
field comments near the Options struct and the related doc block around
namespace_member_prefixes to clarify why these separate maps/sets are needed
instead of relying on the existing namespace_member_prefixes behavior alone.

In `@crates/perry-hir/src/lower_patterns.rs`:
- Around line 162-165: The regex literal lowering in ast::Lit::Regex currently
always calls repair_latin1_decoded_utf8 on re.exp.to_string(), unlike
normalize_swc_string_literal which skips the repair work for ASCII input. Update
the Expr::RegExp path to add the same ASCII fast-path so pure-ASCII regex
patterns return the original string without entering the byte-reinterpretation
loop, while still preserving the existing repair behavior for non-ASCII
patterns.

In `@crates/perry-runtime/src/object/class_registry/state.rs`:
- Around line 510-551: The parent-prototype resolution logic is duplicated in
class_decl_prototype_value and refresh_class_decl_prototype_parent, so extract
it into a shared helper such as resolve_class_parent_prototype_bits(class_id:
u32) -> Option<u64>. Move the existing
dynamic_parent_prototype_bits/get_parent_class_id/builtin_prototype_value_for_class_id/class_decl_prototype_value/global_object_prototype_bits
chain into that helper, then have both call sites use it to compute
parent_proto_bits before object_set_static_prototype.

In `@crates/perry-runtime/src/object/field_get_set/get_field_by_name.rs`:
- Around line 30-57: The get_field_by_name fast path already handles string
receivers for canonical index and length lookups, so remove the duplicated SSO
and heap-string length branches later in the same lookup path. Keep the existing
fallback for constructor in place, and ensure the string-receiver handling
remains centralized in get_field_by_name using the existing string-pointer logic
and JSValue/string index helpers.

In `@crates/perry-runtime/src/object/native_call_method.rs`:
- Around line 1268-1289: The default fallback in native_call_method for
toString/toLocaleString/valueOf is allocating a fresh StringHeader on every call
via js_string_from_bytes. Cache or intern these three method-name keys once (for
example with thread-local storage or OnceLock) and reuse them in the method_name
match path before calling js_object_get_field_by_name, so the common
Object.prototype fallback avoids per-call allocation.

In `@tests/test_dynamic_import_package_registry.sh`:
- Around line 20-33: The package fixture in the dynamic import registry test has
duplicate compilePackages settings with the same value, which is confusing.
Update the package.json fixture used in this test to keep only the correct
schema location for compilePackages and remove the redundant one, using the
perry and compilePackages keys in the test fixture as the reference points.

In `@tests/test_reflect_set_frozen_array.sh`:
- Around line 6-7: The test script’s binary selection and execution flow are
inconsistent with the sibling tests: it prefers the debug build first and uses a
direct run path instead of the usual compile-then-execute pattern. Update the
setup in test_reflect_set_frozen_array.sh to match the other test scripts by
preferring target/release/perry first and falling back to debug, and switch the
invocation to the same compile+execute style used elsewhere so it exercises the
optimized path and --no-auto-optimize coverage consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 36f07724-70bf-4513-96ef-de3e079d22fb

📥 Commits

Reviewing files that changed from the base of the PR and between c7feba7 and c4b6818.

⛔ Files ignored due to path filters (1)
  • tests/release/packages/zod3-basic/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (133)
  • crates/perry-codegen/src/codegen/artifacts.rs
  • crates/perry-codegen/src/codegen/closure.rs
  • crates/perry-codegen/src/codegen/entry.rs
  • crates/perry-codegen/src/codegen/function.rs
  • crates/perry-codegen/src/codegen/helpers.rs
  • crates/perry-codegen/src/codegen/method.rs
  • crates/perry-codegen/src/codegen/mod.rs
  • crates/perry-codegen/src/codegen/module_globals_emit.rs
  • crates/perry-codegen/src/codegen/opts.rs
  • crates/perry-codegen/src/expr/binary.rs
  • crates/perry-codegen/src/expr/dyn_extern_i18n.rs
  • crates/perry-codegen/src/expr/index_get.rs
  • crates/perry-codegen/src/expr/instance_misc1.rs
  • crates/perry-codegen/src/expr/mod.rs
  • crates/perry-codegen/src/expr/property_get.rs
  • crates/perry-codegen/src/expr/this_super_call.rs
  • crates/perry-codegen/src/lower_call/console_promise.rs
  • crates/perry-codegen/src/lower_call/namespace_call.rs
  • crates/perry-codegen/src/lower_call/new.rs
  • crates/perry-codegen/src/lower_call/new_helpers.rs
  • crates/perry-codegen/src/lower_call/property_get.rs
  • crates/perry-codegen/src/lower_call/property_get/number_string.rs
  • crates/perry-codegen/tests/argless_builtin_extra_args.rs
  • crates/perry-codegen/tests/class_keys_gc_root.rs
  • crates/perry-codegen/tests/constructor_recursion.rs
  • crates/perry-codegen/tests/destructure_call_location.rs
  • crates/perry-codegen/tests/large_object_barriers.rs
  • crates/perry-codegen/tests/macos_bundle_chdir_gate.rs
  • crates/perry-codegen/tests/native_proof_buffer_views.rs
  • crates/perry-codegen/tests/native_proof_regressions.rs
  • crates/perry-codegen/tests/shadow_slot_hygiene.rs
  • crates/perry-codegen/tests/static_symbol_hygiene.rs
  • crates/perry-codegen/tests/typed_feedback.rs
  • crates/perry-codegen/tests/typed_shape_descriptor.rs
  • crates/perry-codegen/tests/typed_shape_descriptors.rs
  • crates/perry-hir/src/dynamic_import.rs
  • crates/perry-hir/src/dynamic_import/visitors.rs
  • crates/perry-hir/src/eval_classifier.rs
  • crates/perry-hir/src/lower/closure_analysis.rs
  • crates/perry-hir/src/lower/const_fold_fn.rs
  • crates/perry-hir/src/lower/expr_call/array_only_methods.rs
  • crates/perry-hir/src/lower/expr_call/imported_array_methods.rs
  • crates/perry-hir/src/lower/expr_function.rs
  • crates/perry-hir/src/lower/expr_new.rs
  • crates/perry-hir/src/lower/lower_module_fn.rs
  • crates/perry-hir/src/lower/module_decl.rs
  • crates/perry-hir/src/lower/module_decl/reexports.rs
  • crates/perry-hir/src/lower/stmt.rs
  • crates/perry-hir/src/lower_decl/block.rs
  • crates/perry-hir/src/lower_decl/body_stmt.rs
  • crates/perry-hir/src/lower_decl/enum_decl.rs
  • crates/perry-hir/src/lower_decl/mod.rs
  • crates/perry-hir/src/lower_patterns.rs
  • crates/perry-runtime/src/collection_iter.rs
  • crates/perry-runtime/src/json/replacer.rs
  • crates/perry-runtime/src/object/array_object_ops.rs
  • crates/perry-runtime/src/object/class_registry.rs
  • crates/perry-runtime/src/object/class_registry/construct.rs
  • crates/perry-runtime/src/object/class_registry/parent_static.rs
  • crates/perry-runtime/src/object/class_registry/state.rs
  • crates/perry-runtime/src/object/descriptors.rs
  • crates/perry-runtime/src/object/field_get_set/get_field_by_name.rs
  • crates/perry-runtime/src/object/field_get_set/get_field_by_name_tail.rs
  • crates/perry-runtime/src/object/field_get_set/has_property.rs
  • crates/perry-runtime/src/object/global_this.rs
  • crates/perry-runtime/src/object/global_this/builtin_thunks.rs
  • crates/perry-runtime/src/object/global_this/proto_methods.rs
  • crates/perry-runtime/src/object/instanceof.rs
  • crates/perry-runtime/src/object/mod.rs
  • crates/perry-runtime/src/object/native_call_method.rs
  • crates/perry-runtime/src/object/native_call_method/collection_methods.rs
  • crates/perry-runtime/src/object/native_call_method/common_methods.rs
  • crates/perry-runtime/src/object/native_call_method/primitive_methods.rs
  • crates/perry-runtime/src/object/object_ops/define_properties.rs
  • crates/perry-runtime/src/object/object_ops/define_property.rs
  • crates/perry-runtime/src/object/object_ops/has_own.rs
  • crates/perry-runtime/src/object/object_ops/prototype.rs
  • crates/perry-runtime/src/object/to_string_tag.rs
  • crates/perry-runtime/src/proxy.rs
  • crates/perry-runtime/src/set.rs
  • crates/perry-runtime/src/string/char_ops.rs
  • crates/perry-runtime/src/string/format.rs
  • crates/perry-runtime/src/symbol/get.rs
  • crates/perry-runtime/src/url/mod.rs
  • crates/perry-runtime/src/url/parse.rs
  • crates/perry-runtime/src/value/dyn_index.rs
  • crates/perry-runtime/src/value/dynamic_arith.rs
  • crates/perry-stdlib/src/fetch/dispatch.rs
  • crates/perry/src/commands/compile/collect_modules.rs
  • crates/perry/src/commands/compile/object_cache.rs
  • crates/perry/src/commands/compile/run_pipeline.rs
  • tests/release/packages/zod3-basic/entry.ts
  • tests/release/packages/zod3-basic/expected.txt
  • tests/release/packages/zod3-basic/fixture.sh
  • tests/release/packages/zod3-basic/package.json
  • tests/test_array_not_instanceof_error_subclass.sh
  • tests/test_bigint_samevalue_set.sh
  • tests/test_builtin_prototype_and_fetch_reflection.sh
  • tests/test_class_define_property_name.sh
  • tests/test_dynamic_arithmetic_bigint_any.sh
  • tests/test_dynamic_extends_class_binding.sh
  • tests/test_dynamic_function_probe_fallback_stderr.sh
  • tests/test_dynamic_import_package_registry.sh
  • tests/test_dynamic_parent_builtin_no_downgrade.sh
  • tests/test_error_subclass_arrow_field_init.sh
  • tests/test_export_enum_runtime_object.sh
  • tests/test_file_instanceof.sh
  • tests/test_forward_captured_let_box.sh
  • tests/test_function_prototype_instanceof.sh
  • tests/test_imported_default_named_reexport.sh
  • tests/test_imported_error_new_target.sh
  • tests/test_imported_static_field_alias_method.sh
  • tests/test_json_stringify_replacer_key_order.sh
  • tests/test_map_set_subclass_constructor_iterable.sh
  • tests/test_missing_error_property_undefined.sh
  • tests/test_namespace_default_alias_call.sh
  • tests/test_namespace_homonymous_class_export.sh
  • tests/test_namespace_homonymous_var_function_exports.sh
  • tests/test_namespace_reexport_binding.sh
  • tests/test_namespace_reexport_optional_arg.sh
  • tests/test_namespace_subobject_method_call.sh
  • tests/test_new_parameter_shadows_class.sh
  • tests/test_number_to_string_large_exponent.sh
  • tests/test_object_create_for_in_prototype.sh
  • tests/test_object_create_in_operator_prototype.sh
  • tests/test_object_create_inherited_constructor.sh
  • tests/test_object_prototype_method_call_fallback.sh
  • tests/test_own_to_string_method.sh
  • tests/test_reflect_set_frozen_array.sh
  • tests/test_string_computed_length.sh
  • tests/test_unicode_regex_literal.sh
  • tests/test_url_href_normalization.sh
  • tests/test_url_instanceof_reflection.sh

Comment thread crates/perry-codegen/src/codegen/artifacts.rs
Comment thread crates/perry-codegen/src/codegen/method.rs
Comment thread crates/perry-hir/src/dynamic_import.rs Outdated
Comment thread crates/perry-hir/src/lower_decl/body_stmt.rs
Comment thread crates/perry-hir/src/lower_decl/body_stmt.rs Outdated
Comment thread crates/perry/src/commands/compile/run_pipeline.rs Outdated
Comment thread crates/perry/src/commands/compile/run_pipeline.rs
}));
TS

node "$TMPDIR/main.ts" > "$TMPDIR/expected.log"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

What is the minimum Node.js version required to run a .ts file directly without any CLI flags?

💡 Result:

The minimum Node.js version required to run a.ts file directly without any CLI flags is Node.js 23.6.0 [1][2]. Starting with version 23.6.0, Node.js includes stable, built-in support for "type stripping," which allows the Node.js runtime to execute TypeScript files directly by removing type annotations and executing the resulting JavaScript [3][2]. This feature eliminates the need for external tools like ts-node or tsx for basic execution [4][5]. Key details regarding this feature: - It performs type stripping only; no type checking is performed [6][7]. - It does not support features that require code generation (such as enums or namespaces) unless the --experimental-transform-types flag is used [1]. - It is intended to be a lightweight way to execute TypeScript files and does not replace full-featured compilers for complex projects requiring tsconfig.json configurations [6][1].

Citations:


🏁 Script executed:

git ls-files | rg '(^|/)(test_builtin_prototype_and_fetch_reflection\.sh|test_array_not_instanceof_error_subclass\.sh|test_number_to_string_large_exponent\.sh|\.github/workflows/|package\.json|README|package-lock\.json|pnpm-lock\.yaml|yarn\.lock)$'

Repository: PerryTS/perry

Length of output: 3245


🏁 Script executed:

sed -n '1,220p' tests/test_builtin_prototype_and_fetch_reflection.sh

Repository: PerryTS/perry

Length of output: 2923


🏁 Script executed:

sed -n '1,220p' tests/test_array_not_instanceof_error_subclass.sh
printf '\n---\n'
sed -n '1,220p' tests/test_number_to_string_large_exponent.sh

Repository: PerryTS/perry

Length of output: 5588


🏁 Script executed:

git ls-files .github/workflows package.json README* | xargs -r -I{} sh -c 'echo "--- {}"; sed -n "1,220p" "{}"'

Repository: PerryTS/perry

Length of output: 50371


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path
for p in [
    Path("tests/test_builtin_prototype_and_fetch_reflection.sh"),
    Path("tests/test_array_not_instanceof_error_subclass.sh"),
    Path("tests/test_number_to_string_large_exponent.sh"),
]:
    print(f"--- {p} ---")
    if p.exists():
        text = p.read_text()
        for i, line in enumerate(text.splitlines(), 1):
            if "node \"$TMPDIR/main.ts\"" in line or "expected.log" in line or "TMPDIR/main.ts" in line:
                print(f"{i}: {line}")
    else:
        print("missing")
PY

Repository: PerryTS/perry

Length of output: 1091


🏁 Script executed:

rg -n "setup-node|node-version|tests/test_builtin_prototype_and_fetch_reflection.sh|test_array_not_instanceof_error_subclass.sh|test_number_to_string_large_exponent.sh" .github tests -g '*.yml' -g '*.yaml' -g '*.sh'

Repository: PerryTS/perry

Length of output: 1803


🏁 Script executed:

rg -n "tests/test_.*\.sh|bash tests/|sh tests/|./tests/" .github -g '*.yml' -g '*.yaml'

Repository: PerryTS/perry

Length of output: 648


🏁 Script executed:

git ls-files .github/workflows/test.yml .github/workflows/simctl-tests.yml .github/workflows/*.yml | sed -n '1,120p'

Repository: PerryTS/perry

Length of output: 647


🏁 Script executed:

rg -n "test_builtin_prototype_and_fetch_reflection\.sh|test_array_not_instanceof_error_subclass\.sh|test_number_to_string_large_exponent\.sh" .

Repository: PerryTS/perry

Length of output: 151


🏁 Script executed:

ast-grep outline .github/workflows/test.yml --view expanded

Repository: PerryTS/perry

Length of output: 192


🏁 Script executed:

sed -n '620,760p' .github/workflows/test.yml

Repository: PerryTS/perry

Length of output: 6195


🏁 Script executed:

rg -n "bash tests/|tests/test_.*\.sh|shell tests|node-version|setup-node" .github/workflows/test.yml .github/workflows/simctl-tests.yml

Repository: PerryTS/perry

Length of output: 876


🏁 Script executed:

sed -n '640,740p' .github/workflows/test.yml

Repository: PerryTS/perry

Length of output: 4253


🏁 Script executed:

rg -n "tests/.*\.sh|bash .*tests|sh .*tests|\.sh" scripts .github/workflows tests -g '*.py' -g '*.sh' -g '*.yml' -g '*.yaml'

Repository: PerryTS/perry

Length of output: 28464


🏁 Script executed:

rg -n "bash tests/|tests/test_.*\\.sh|shell tests|node-version|setup-node" .github/workflows/test.yml .github/workflows/simctl-tests.yml

Repository: PerryTS/perry

Length of output: 876


🏁 Script executed:

rg -n "tests/.*\\.sh|bash .*tests|sh .*tests|\\.sh" scripts .github/workflows tests -g '*.py' -g '*.sh' -g '*.yml' -g '*.yaml'

Repository: PerryTS/perry

Length of output: 28464


Avoid using node "$TMPDIR/main.ts" for the oracle in tests/test_builtin_prototype_and_fetch_reflection.sh:61. It depends on the host Node having direct .ts support; on older releases this fails before the diff. Hardcode the expected log like the sibling tests, or pin the Node version/flags used here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_builtin_prototype_and_fetch_reflection.sh` at line 61, The oracle
in tests/test_builtin_prototype_and_fetch_reflection.sh currently uses node
"$TMPDIR/main.ts", which depends on host Node supporting direct TypeScript
execution. Update the test to avoid this runtime dependency by hardcoding the
expected log like the sibling tests, or by using the same pinned Node
version/flags as the rest of the suite; keep the change focused in the test
script around the main.ts oracle setup.

Comment thread tests/test_unicode_regex_literal.sh
Comment thread tests/test_url_href_normalization.sh
@TheHypnoo TheHypnoo marked this pull request as ready for review July 3, 2026 08:56

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
crates/perry-hir/src/lower_decl/static_init.rs (1)

94-154: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Consider extracting the shared class-body iteration skeleton.

build_interleaved_static_init_exprs duplicates the member-walk (ClassProp/PrivateProp/StaticBlock with parallel field_idx/block_idx counters) already present in build_interleaved_static_init_stmts (Lines 31-92) and, partially, build_fresh_class_static_init (Lines 156-216). Three copies of this walk can silently diverge. A single generic driver taking per-field / per-block callbacks would collapse them. Note the emit differences are intentional and must be preserved: the expr variant uses RegisterClassStaticSymbol (sequenced-in-front, per #894) vs the stmt variant's ClassStaticSymbolSet.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-hir/src/lower_decl/static_init.rs` around lines 94 - 154,
Refactor the duplicated class-body walk in build_interleaved_static_init_exprs
by reusing a shared iteration driver with callbacks for field and static-block
handling, so it stays aligned with build_interleaved_static_init_stmts and
build_fresh_class_static_init. Keep the current sequencing and symbol behavior
intact: the expr path must still emit RegisterClassStaticSymbol for keyed fields
and StaticFieldSet for plain fields, while static blocks should still map
through the per-block __perry_static_init_* naming and only emit when the
matching method exists.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/perry-hir/src/lower_decl/static_init.rs`:
- Around line 94-154: Refactor the duplicated class-body walk in
build_interleaved_static_init_exprs by reusing a shared iteration driver with
callbacks for field and static-block handling, so it stays aligned with
build_interleaved_static_init_stmts and build_fresh_class_static_init. Keep the
current sequencing and symbol behavior intact: the expr path must still emit
RegisterClassStaticSymbol for keyed fields and StaticFieldSet for plain fields,
while static blocks should still map through the per-block __perry_static_init_*
naming and only emit when the matching method exists.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9f5b9206-cca9-4a27-b6ec-3f429db539e8

📥 Commits

Reviewing files that changed from the base of the PR and between 87f86e4 and b2e810e.

📒 Files selected for processing (5)
  • crates/perry-hir/src/lower/lower_expr/arm_class.rs
  • crates/perry-hir/src/lower_decl/body_stmt.rs
  • crates/perry-hir/src/lower_decl/mod.rs
  • crates/perry-hir/src/lower_decl/static_init.rs
  • tests/test_dynamic_class_extends_once.sh
🚧 Files skipped from review as they are similar to previous changes (3)
  • crates/perry-hir/src/lower_decl/mod.rs
  • crates/perry-hir/src/lower_decl/body_stmt.rs
  • tests/test_dynamic_class_extends_once.sh

TheHypnoo added 4 commits July 3, 2026 13:06
…t key equality to Map

- js_object_has_property: the prototype-chain tail now recurses through the
  entry point; bound total depth to 1024 (the same limit the manual walk uses)
  so a pathologically deep chain, or a cycle slipping past setPrototypeOf's
  cycle check, cannot overflow the stack.
- map.rs jsvalue_eq: compare BigInt keys by value (js_bigint_eq), mirroring
  the Set path, so new Map([[1n,'a']]).get(1n) resolves across allocations.
- Restore the #3986 synthetic-prototype rationale comment at both moved sites.
… in closure-capture analysis

- map_set_default_super_kind: bound the extends-chain walk at 64 (matching its
  twin in method.rs and ctor_chain_uses_new_target) so a cyclic constructorless
  extends graph cannot loop forever.
- collect_closure_captures_expr: descend into ClassExprFresh (statics +
  captured args), InstanceOf, In, and tagged-template exprs. ClassExprFresh is
  reachable via the class-extends binding path, so a closure capturing a
  forward-declared let there must be seen or its box is not preallocated.
- property_get: reword the trim/case-method dispatch comment to be
  library-agnostic (no Zod name); logic unchanged.
- namespace_call: document the arity-1 padding fallback for renamed re-exports.
- dynamic_arith: note that '+' intentionally skips ToNumber coercion.
- builtin_thunks: drop the unused _body computation on the dynamic-Function path.
- tests: SKIP (not FAIL) when node is absent in six node-oracle tests; make the
  non-canonical index assertion in test_string_computed_length explicit.
…ssion tests

The tests/test_*.sh regression suite copy-pastes ~40 lines of perry-binary and
runtime-lib detection, temp-dir, compile/run error handling, and diff logic
into every file. Add a sourced _perry_test_lib.sh (leading underscore keeps it
out of run_tests.sh's glob, matching the fixture harness convention) exposing
perry_run / perry_write / perry_expect / perry_expect_node / perry_pass.

Convert test_object_create_for_in_prototype (literal oracle, 72->26 lines) and
test_url_instanceof_reflection (node oracle, 69->21 lines) as the adoption
pattern; remaining tests can migrate incrementally.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/_perry_test_lib.sh`:
- Around line 29-30: Make the binary and runtime-lib selection in the test
harness use the same resolved build profile, because PERRY currently prefers
release while perry_run resolves runtime archives in the opposite order. Update
the shared selection logic in tests/_perry_test_lib.sh around PERRY and
perry_run so both choose either debug or release consistently from one profile
decision, and then use that same profile for the binary and the runtime/stdlib
archives.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 29972d64-ceb1-4b59-8b0a-aed56226a4d7

📥 Commits

Reviewing files that changed from the base of the PR and between 86dbcc3 and 7d696ce.

📒 Files selected for processing (3)
  • tests/_perry_test_lib.sh
  • tests/test_object_create_for_in_prototype.sh
  • tests/test_url_instanceof_reflection.sh

Comment thread tests/_perry_test_lib.sh
TheHypnoo added 2 commits July 3, 2026 14:09
js_switch_strict_equals special-cased strings and numbers, then fell through to
bit-identity — so `switch (1n + 0n) { case 1n: }` never matched (two distinct
allocations). Add a BigInt value-compare arm (js_bigint_eq), mirroring the
Set/Map key and general === paths; a BigInt is never === a non-BigInt.
…subclasses

A class extending a builtin (Error/Map/Set/Array) with no explicit constructor
dropped its instance field initializers — `class E extends Error { tag = "e" }`
gave new E().tag === 0 instead of "e". The post-super field-init fallback used
AfterRoot (chain[1..]), but with a builtin parent the leaf is the sole user-class
chain entry, so AfterRoot applied nothing. Use SelfOnly when the parent is not a
user class, matching the explicit-super() arm in this_super_call.rs. The explicit
-constructor path is unaffected.
TheHypnoo added 3 commits July 3, 2026 14:19
instanceof.rs hardcoded the Blob/File/URL class-ids as inline hex in the
name->id match and re-declared them as function-local consts, while the error
ids in the same match already reference crate::error::CLASS_ID_*. Give Blob/File
a canonical home in node_submodules::blob and URL one in the url module (matching
the per-module CLASS_ID_* convention), and reference them everywhere so the three
ids have a single source of truth. Values unchanged; compiler-verified identical.

(Left untouched: the local CLASS_ID_RESPONSE/REQUEST/HEADERS block, whose values
coincide numerically with weakref ids — likely distinct handle-band vs ObjectHeader
namespaces, worth a separate look.)
…_test_lib.sh

CodeRabbit: binary selection preferred release-then-debug while the runtime-lib
selection checked debug-then-release, so a release binary could pair with debug
static libs. Derive PERRY_RUNTIME_DIR from the chosen binary's own target dir
(matching the pattern already used in test_builtin_prototype_and_fetch_reflection.sh)
so the two never mismatch; fall back to auto-optimize when that dir has no libs.
…h copies

The two copies of map_set_default_super_kind (codegen/method.rs and
lower_call/new_helpers.rs) capped the extends-chain walk at 32 and 64
respectively. Unify both at 64 (matching the neighboring
ctor_chain_uses_new_target) and add a cross-reference note so the twins stay in
sync. Guard-only; the bound is unreachable for real class hierarchies.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants