fix(hir): construct real intrinsic for new globalThis.<Builtin>() (#6726)#6730
Conversation
…erryTS#6726) `new globalThis.Set()` compiled but produced an empty-object placeholder with none of Set's methods, so `new globalThis.Set().has(x)` threw "has is not a function". The plain `new Set()` and local-alias `const S = globalThis.Set; new S()` forms both worked — the failure was specific to the inline member-expression callee. Root cause: the bare-identifier `new <Builtin>()` arm routes every builtin to its real allocator (`Set` → `Expr::SetNew`, `Map` → `MapNew`, `Date` → `DateNew`, the Error family, typed arrays, boxed primitives, `Proxy`, `WeakRef`, …), but a member-expression callee never reaches it. It fell through to `lower_new_non_ident` → `Expr::NewDynamic`, and for the data-structure builtins — whose construction lives in a dedicated HIR variant rather than codegen's `lower_builtin_new` — codegen's `try_static_class_name` rerouted `globalThis.Set` to `lower_new("Set")`, which has no `Set` arm and dead-ended at a `class_id=0` empty object. Fix: in `lower_new`, detect the `new globalThis.<Ident>(...)` callee (the real global object — `globalThis` not lexically rebound — and a recognized built-in constructor name) and re-dispatch through a synthesized bare-identifier `new <Ident>(...)`, so a member callee constructs the exact same intrinsic as the plain form. Placed after `lower_new_member_native`, so the already-working member paths (URL/streams/fetch/worker) are untouched. A shadowing local (`const globalThis = {...}`) suppresses the re-dispatch and keeps using the local object, matching Node. Covered end-to-end (byte-for-byte vs Node) in test_gap_new_globalthis_builtin_6726.ts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThe lowering logic recognizes eligible unshadowed ChangesGlobalThis built-in constructors
Estimated code review effort: 3 (Moderate) | ~20 minutes Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant Source as new globalThis.<Builtin>(...)
participant Lowering as lower_new
participant Intrinsic as Builtin intrinsic path
Source->>Lowering: lower member-expression constructor
Lowering->>Lowering: verify globalThis and whitelist constructor
Lowering->>Lowering: re-dispatch as new <Builtin>(...)
Lowering->>Intrinsic: lower with intrinsic handling
Intrinsic-->>Source: emit intrinsic allocation
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
test-files/test_gap_new_globalthis_builtin_6726.ts (1)
53-60: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAdd regression tests for local shadowing.
Please add test cases to ensure that
new globalThis.Set()behaves correctly even when bothglobalThisand the built-in (Set) are locally shadowed. This will safeguard against the bugs in the lowering logic and guarantee thatglobalThisexplicitly constructs the intrinsic.🧪 Suggested test cases
// 1. globalThis should bypass local shadowing of the built-in { class Set { constructor() { (this as any).isLocal = true; } } const s = new globalThis.Set([1, 2]); console.log(s.has(1), (s as any).isLocal); // Expected: true, undefined } // 2. A locally shadowed globalThis should not construct the intrinsic { const globalThis = { Set: class { constructor() { (this as any).isFake = true; } } }; const s = new globalThis.Set(); console.log((s as any).isFake); // Expected: 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 `@test-files/test_gap_new_globalthis_builtin_6726.ts` around lines 53 - 60, Add regression cases in test_gap_new_globalthis_builtin_6726.ts covering new globalThis.Set() with a locally shadowed Set, verifying it constructs the intrinsic and supports Set behavior, and a separately scoped locally shadowed globalThis, verifying that new globalThis.Set() constructs the shadowed fake implementation. Follow the suggested scoped setups and assertions while preserving the existing tests.
🤖 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-hir/src/lower/expr_new.rs`:
- Around line 145-179: The globalThis rewrite in lower_new has incomplete
globalThis shadowing validation and allows the synthesized built-in callee to be
shadowed lexically. Replace lookup_local("globalThis") with comprehensive
non-class and class binding checks using shadows_unqualified_global and
lookup_class, and route the synthesized constructor through built-in lowering
with force_unshadowed behavior so local bindings such as class Set cannot
override the global intrinsic.
---
Outside diff comments:
In `@test-files/test_gap_new_globalthis_builtin_6726.ts`:
- Around line 53-60: Add regression cases in
test_gap_new_globalthis_builtin_6726.ts covering new globalThis.Set() with a
locally shadowed Set, verifying it constructs the intrinsic and supports Set
behavior, and a separately scoped locally shadowed globalThis, verifying that
new globalThis.Set() constructs the shadowed fake implementation. Follow the
suggested scoped setups and assertions while preserving the existing tests.
🪄 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: 4ab5c68d-9e38-462e-ab90-a201051928d5
📒 Files selected for processing (3)
crates/perry-hir/src/lower/expr_new.rscrates/perry-hir/src/lower/expr_new_builtins.rstest-files/test_gap_new_globalthis_builtin_6726.ts
…bypass (PerryTS#6726) Address CodeRabbit review on PR PerryTS#6730: 1. Comprehensive `globalThis` shadow check. The guard used `lookup_local("globalThis")`, which misses a `function`/`class`/imported `globalThis` rebinding the name — those would wrongly trigger the rewrite. Use `shadows_unqualified_global("globalThis")` (locals + funcs + imports + classes), so any rebinding suppresses the rewrite and the member reads the user binding, matching Node. 2. `globalThis.Set` is the intrinsic even under a lexical `class Set {}`. The re-dispatch synthesizes a bare `new Set()` and recurses, which would otherwise re-enter `lower_new`'s `shadowed_by_user_binding` check and resolve to the user class. Add a one-shot `global_intrinsic_new_once` flag (consumed at the top of `lower_new`) that suppresses the local snapshot and the shadow flag for the re-dispatched callee only, so the built-in arm fires. The flag never leaks into argument or nested-`new` lowering. Extends test_gap_new_globalthis_builtin_6726.ts with both shadowing cases (block-local `class Set` → intrinsic; rebound `const globalThis` → local object's Set), byte-for-byte identical to Node. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks @coderabbitai — both points addressed in 26ff6b3:
Both of your suggested test cases were added to
|
|
Tip For best results, initiate chat on the files or code changes.
🐇 ✅ ✏️ Learnings added
If you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit! |
Summary
Fixes #6726.
new globalThis.Set()compiled but produced an object without any Set methods —new globalThis.Set().has(42)threwTypeError: has is not a function. The plainnew Set()and the local-aliasconst S = globalThis.Set; new S()forms both worked, so the failure was specific to constructing through the inline member-expression callee.Root cause
The bare-identifier
new <Builtin>()arm inlower_newroutes every built-in constructor to its real allocator —Set→Expr::SetNew,Map→MapNew,Date→DateNew, the Error family, typed arrays, boxed primitives,Proxy,WeakRef, and so on. A member-expression callee (globalThis.Set) never reaches that arm: it falls through tolower_new_non_ident→Expr::NewDynamic.For the data-structure builtins — whose construction lives in a dedicated HIR variant rather than in codegen's
lower_builtin_new— codegen'stry_static_class_namethen rerouted thePropertyGet { globalThis, "Set" }callee tolower_new("Set"), which has noSet/Map/Datearm and dead-ended at aclass_id=0empty-object placeholder. (Theconst S = globalThis.Set; new S()form works because aLocalGetcallee instead reaches the runtimejs_new_function_constructhelper, which does recognise the reified global builtin thunks.)Fix
In
lower_new, detect thenew globalThis.<Ident>(...)callee — the real global object (globalThisnot lexically rebound) with a recognised built-in constructor name — and re-dispatch through a synthesized bare-identifiernew <Ident>(...). A member callee then constructs the exact same intrinsic as the plain form.lower_new_member_native, so the already-working member paths (URL/ streams / fetch / worker-messaging) are untouched.const globalThis = { Set: ... }) suppresses the re-dispatch via thectx.lookup_local("globalThis").is_none()guard, so it keeps using the local object — matching Node.Ident, not aMember.Testing
test-files/test_gap_new_globalthis_builtin_6726.tscovers the repro plusSet/Map/Date/WeakMap/WeakSet/TypeError/Array/PromisethroughglobalThis, and the two forms that already worked (bare + local alias) as regression guards. Perry output is byte-for-byte identical to Node.Verified manually against Node, all matching:
cargo test -p perry-hir --libpasses (247/247). No version bump / changelog per the contributor-PR convention.🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
new globalThis.<Builtin>(...)construction to matchnew <Builtin>(...)for supported JavaScript and Web/DOM built-in constructors.globalThis.Tests
Set,Map,Date,WeakMap/WeakSet,Array,Promise, and more) and verifying equivalent behavior across the different construction forms.