Skip to content

fix(hir): aliased ESM import of a native class keeps its native-class identity#5472

Merged
proggeramlug merged 2 commits into
mainfrom
fix/aliased-native-class-import
Jun 20, 2026
Merged

fix(hir): aliased ESM import of a native class keeps its native-class identity#5472
proggeramlug merged 2 commits into
mainfrom
fix/aliased-native-class-import

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Symptom

import { BlockList as Wj4 } from "net"; const q = new Wj4(); q.addSubnet(...) threw TypeError: addSubnet is not a function, while the un-aliased import { BlockList } from "net" worked. So new <alias>() wasn't recognized as the native class — it built a generic object whose native methods don't exist.

Root cause

Native-class resolution keyed on the local binding name instead of the imported export name at two HIR sites:

  1. crates/perry-hir/src/lower/expr_new.rs (bare-ident new <Ident>()): new Wj4() lowered to Expr::New { class_name: "Wj4" }; codegen's builtin-New dispatch recognizes classes by literal name ("BlockList", "Socket", "SocketAddress", …), so "Wj4" missed every arm and produced an empty placeholder.
  2. crates/perry-hir/src/destructuring/var_decl.rs: register_native_instance tagged the binding ("net","Wj4"), so q.<method>() missed the ("net","BlockList") dispatch rows.

lookup_native_module was already alias-aware (the named-import lowering registers local → (module, imported)), but both call sites discarded the imported name.

Fix

Both sites resolve the local binding to its imported export name (via lookup_native_module) before matching/registering — guarded so user classes/locals shadow correctly and non-native user imports don't over-trigger. The aliased form now produces HIR byte-identical to the un-aliased form. General across all aliased native classes (net BlockList/Socket, http Server, url URL, …).

Validation

  • New e2e crates/perry/tests/aliased_native_class_import.rs (5 tests): URL/Socket/BlockList aliases resolve, aliased==un-aliased HIR, user-import not over-triggered. 5 passed.
  • cargo test -p perry-hir -p perry-codegen: 642 passed, 0 failed.
  • Repros perry==node: URL as U (.host/.pathname), Socket as Sk, BlockList as Wj4 (addSubnet/check).

Part of bringing up @anthropic-ai/claude-code natively (the bundle imports { BlockList as Wj4 } from "net").

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Fixed construction and method dispatch for aliased imports of native built-in classes, ensuring canonical native identity is used instead of the local alias.
    • Improved correctness for native constructor dispatch checks across several native modules.
  • Tests

    • Added regression tests that validate print-hir lowering and runtime behavior for aliased native classes, including positive and negative scenarios.

… identity

`import { BlockList as Wj4 } from "net"; new Wj4()` threw 'addSubnet is not a
function' while the un-aliased `import { BlockList }` worked. Native-class
resolution keyed on the LOCAL binding name instead of the imported export name
at two sites:

- expr_new.rs (bare-ident `new <Ident>()`): `new Wj4()` lowered to
  `Expr::New { class_name: "Wj4" }`; codegen recognizes builtins by literal
  name ("BlockList", "Socket", ...), so "Wj4" missed every arm and built an
  empty placeholder object.
- destructuring/var_decl.rs: `register_native_instance` tagged the binding
  `("net","Wj4")`, missing the `("net","BlockList")` dispatch rows.

`lookup_native_module` was already alias-aware; both sites now resolve the local
binding to its imported export name before matching/registering (guarded so user
classes/locals shadow and non-native user imports don't over-trigger). Aliased
HIR is now byte-identical to the un-aliased form. Fixes all aliased native
classes (net BlockList/Socket, http Server, url URL, ...). Adds e2e test.
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d186e4ff-88f5-4db6-830c-1802aab24fad

📥 Commits

Reviewing files that changed from the base of the PR and between f2c0362 and 35c2574.

📒 Files selected for processing (1)
  • crates/perry/tests/aliased_native_class_import.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/perry/tests/aliased_native_class_import.rs

📝 Walkthrough

Walkthrough

Adds alias-aware resolution for native built-in classes across the Perry HIR pipeline. When a native class is imported under a local alias (e.g., import { BlockList as Wj4 } from "net"), the new lowering and var_decl tagging now rewrite the alias to the canonical export name so builtin-New dispatch and native method tagging key off the correct class identity. Four as_deref() fixes to native module method comparisons are also included, along with comprehensive regression tests validating both HIR identity and runtime behavior.

Changes

Alias-aware native class resolution

Layer / File(s) Summary
as_deref() method comparisons and class_name mutability
crates/perry-hir/src/lower/expr_new.rs
Updates module, events, dns, and vm native module method matching to use as_deref() for dereferenced string comparison; makes class_name mutable to allow alias rewriting downstream.
Core alias-to-export-name rewrite
crates/perry-hir/src/lower/expr_new.rs, crates/perry-hir/src/destructuring/var_decl.rs
Adds the block that detects uppercase-first aliased named imports, calls lookup_native_module, and rewrites class_name to the resolved export name when not shadowed. Updates var_decl.rs native-instance tagging to derive the hardcoded class name from the resolved export name instead of the local alias.
Behavior-preserving refactors in var_decl.rs
crates/perry-hir/src/destructuring/var_decl.rs
Refactors is_function_expr_init to map_or(false, ...), rewrites the ("stream", "from") arm as a block-style if, and reformats LocalGet(src_local) prototype alias tracking; all preserve prior behavior.
Regression tests
crates/perry/tests/aliased_native_class_import.rs
Adds compile_and_run and print_hir helpers plus tests covering: HIR canonical tagging for aliased net.BlockList, runtime correctness for aliased url.URL and net.Socket, runtime equivalence between aliased and un-aliased net.Socket, and a negative test ensuring user-defined class aliases remain unaffected.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

A bunny renamed its net import one day,
BlockList as Wj4—a fancy alias at play.
But perry now looks past the local disguise,
Resolves to the canonical name, oh so wise.
🐇 No alias shall fool the dispatch again!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main fix: aliased native class imports now retain their native identity.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering symptom, root cause, fix, and validation; all required template sections are properly addressed.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/aliased-native-class-import

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

@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 `@crates/perry/tests/aliased_native_class_import.rs`:
- Around line 70-79: The `print_hir` function is returning stdout without
verifying that the `perry compile --print-hir` command executed successfully.
Add a status check after the `.output()` call to assert the command succeeded,
and if it fails, surface the stderr output for better diagnostics. Mirror the
status assertion pattern used in the `compile_and_run` function to ensure
compiler errors are visible when the HIR compilation fails, rather than silently
returning incomplete stdout.
🪄 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: 56e3e352-50ec-4f63-b52a-ddf7d0e4bfb4

📥 Commits

Reviewing files that changed from the base of the PR and between e32321a and f2c0362.

📒 Files selected for processing (3)
  • crates/perry-hir/src/destructuring/var_decl.rs
  • crates/perry-hir/src/lower/expr_new.rs
  • crates/perry/tests/aliased_native_class_import.rs

Comment on lines +70 to +79
let out = Command::new(perry_bin())
.current_dir(dir)
.arg("compile")
.arg(&entry)
.arg("--print-hir")
.arg("-o")
.arg(&output)
.output()
.expect("run perry compile --print-hir");
String::from_utf8_lossy(&out.stdout).into_owned()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Assert --print-hir compile success before returning output.

print_hir currently returns stdout even if perry compile --print-hir fails, which weakens failure diagnostics in the HIR assertions. Mirror the status/assert pattern used in compile_and_run so failures surface the compiler stderr directly.

Suggested patch
 fn print_hir(dir: &Path, source: &str) -> String {
@@
     let out = Command::new(perry_bin())
         .current_dir(dir)
         .arg("compile")
         .arg(&entry)
         .arg("--print-hir")
         .arg("-o")
         .arg(&output)
         .output()
         .expect("run perry compile --print-hir");
+    assert!(
+        out.status.success(),
+        "perry compile --print-hir failed\nstdout:\n{}\nstderr:\n{}",
+        String::from_utf8_lossy(&out.stdout),
+        String::from_utf8_lossy(&out.stderr)
+    );
     String::from_utf8_lossy(&out.stdout).into_owned()
 }
🤖 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/tests/aliased_native_class_import.rs` around lines 70 - 79, The
`print_hir` function is returning stdout without verifying that the `perry
compile --print-hir` command executed successfully. Add a status check after the
`.output()` call to assert the command succeeded, and if it fails, surface the
stderr output for better diagnostics. Mirror the status assertion pattern used
in the `compile_and_run` function to ensure compiler errors are visible when the
HIR compilation fails, rather than silently returning incomplete stdout.

@proggeramlug proggeramlug merged commit 87273f1 into main Jun 20, 2026
15 checks passed
@proggeramlug proggeramlug deleted the fix/aliased-native-class-import branch June 20, 2026 04:46
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.

1 participant