fix(symbol): resolve well-known symbols via optional chaining Symbol?.iterator (#6719)#6729
Conversation
…?.iterator` (PerryTS#6719) `Symbol?.iterator` (and `Symbol?.["iterator"]` / `Symbol?.[name]`) returned `undefined` because only the non-optional dot (`Symbol.iterator`) and bracket (`Symbol["iterator"]`, PerryTS#6676) forms were folded to the well-known-symbol sentinel in `lower_member_inner`. The optional-chaining forms lower through a separate arm (`arm_optchain.rs`) that never routed through that resolver, so a `?.` read fell through to a generic property get on the `Symbol` constructor and returned `undefined`. Downstream, a class/object keyed with `[Symbol?.iterator]` was keyed under `undefined` and so was not iterable (`[...new R()]` → "value is not iterable"). - HIR: extract the Symbol well-known resolver from `lower_member_inner` into a shared `try_fold_symbol_well_known_member(ctx, obj, prop)` covering the dot, string-bracket, and runtime-key (`js_symbol_computed_member`) forms, and wire it into the `OptChainBase::Member` arm. `Symbol` is a non-nullish global, so `Symbol?.iterator === Symbol.iterator` and the `?.` short-circuit is dead — the resolver returns the fold directly. - The extracted resolver also applies the `PerryTS#6676` real-global gate (`lookup_local("Symbol").is_none()`) to the dot form, which previously lacked it: a locally-shadowed `Symbol` now resolves normally instead of folding. - test: test_gap_6719_optional_chain_symbol.ts (byte-identical to Node) — optional dot/bracket/runtime-key forms, class & object-literal computed method keys, non-well-known keys, local shadowing, and nullish short-circuit. Co-authored-by: Ralph <ralph@skelpo.com> 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 (2)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThis change centralizes well-known ChangesOptional well-known Symbol access
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Sequence Diagram(s)sequenceDiagram
participant Source
participant OptionalChainLowering
participant SymbolMemberHelper
participant FoldedExpression
Source->>OptionalChainLowering: lower Symbol?.member
OptionalChainLowering->>SymbolMemberHelper: resolve well-known Symbol member
SymbolMemberHelper->>FoldedExpression: create folded symbol expression
FoldedExpression-->>OptionalChainLowering: return expression
🚥 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
🧹 Nitpick comments (1)
test-files/test_gap_6719_optional_chain_symbol.ts (1)
78-83: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd
class Symbolandfunction Symbolshadowing cases.const Symbolonly covers one binding form; these should also keepSymbol?.iteratoron the local binding.🤖 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_6719_optional_chain_symbol.ts` around lines 78 - 83, Extend the optional-chain shadowing coverage in shadowed() with separate class Symbol and function Symbol declarations, verifying that Symbol?.iterator continues resolving to each local binding rather than folding the global symbol. Preserve the existing const Symbol case and expected runtime-value assertions.Source: Path instructions
🤖 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_member.rs`:
- Line 160: Update the Symbol shadowing condition in the member-expression
lowering logic to use ctx.shadows_unqualified_global("Symbol") instead of
ctx.lookup_local("Symbol"). Preserve the existing obj_ident.sym check while
ensuring all binding types that shadow the global Symbol are detected.
---
Nitpick comments:
In `@test-files/test_gap_6719_optional_chain_symbol.ts`:
- Around line 78-83: Extend the optional-chain shadowing coverage in shadowed()
with separate class Symbol and function Symbol declarations, verifying that
Symbol?.iterator continues resolving to each local binding rather than folding
the global symbol. Preserve the existing const Symbol case and expected
runtime-value assertions.
🪄 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: 4f72706d-bad8-4792-a5ad-831059be4a78
📒 Files selected for processing (3)
crates/perry-hir/src/lower/expr_member.rscrates/perry-hir/src/lower/lower_expr/arm_optchain.rstest-files/test_gap_6719_optional_chain_symbol.ts
PerryTS#6729) The shared resolver gated on `lookup_local("Symbol").is_none()` (inherited from PerryTS#6676), which only detects `let`/`const`/param bindings — a `class Symbol {}` or `function Symbol() {}` shadow slipped through and still folded `Symbol.iterator` to the global well-known symbol (`class Symbol { static iterator }; Symbol?.iterator` printed `Symbol(Symbol.iterator)` instead of the class's static field). Switch to `shadows_unqualified_global("Symbol")`, the established idiom in this file (used for `process`), which is a strict superset covering local, function, imported- function, and class bindings. Fixes the dot, bracket (PerryTS#6676), and optional-chain (PerryTS#6719) forms in one place. - test: add `class Symbol` and `function Symbol` shadowing cases (byte-identical to Node) alongside the existing `const Symbol` case. Co-authored-by: Ralph <ralph@skelpo.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks @coderabbitai — both addressed in 46709b9:
Re-validated: gap test byte-identical to Node, and an in-worktree A/B (65 Symbol/optional-chaining gap tests, node held constant) is byte-neutral vs the base — only the target test flips fail→pass. |
|
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! |
Fixes #6719. Follow-up to #6676 (computed
Symbol["iterator"]).Problem
Optional-member access of a well-known symbol on the
Symbolconstructor returnedundefined:#6676 fixed the bracket form
Symbol["iterator"], and(0, Symbol.iterator)also worked, but the optional-chaining formSymbol?.iteratorstill returnedundefined. Downstream, a class/object keyed with a well-known symbol reached via?.was keyed underundefinedand therefore not iterable:Root cause
The dot (
Symbol.iterator) and string-bracket (Symbol["iterator"], #6676) forms fold to the@@__perry_wk_<name>well-known-symbol sentinel inlower_member_inner(expr_member.rs). The optional-chaining forms lower through a separate arm (arm_optchain.rs,OptChainBase::Member) that never routed through that resolver — so a?.read fell through to a generic property get on theSymbolconstructor and returnedundefined.Fix
lower_member_innerinto a sharedtry_fold_symbol_well_known_member(ctx, obj, prop)— one resolver for the dot, string-bracket, and runtime-key (js_symbol_computed_member) forms — and wire it into the optional-chainMemberarm.Symbolis a non-nullish global, soSymbol?.iterator === Symbol.iteratorand the?.short-circuit is dead; the resolver returns the fold directly.lookup_local("Symbol").is_none()) to the dot form, which previously lacked it: a locally-shadowedSymbolnow resolves normally instead of folding (strict correctness improvement, covered by the new test's shadow case).No runtime changes —
js_symbol_computed_memberalready exists (#6676).Validation
test_gap_6719_optional_chain_symbol.tsis byte-identical to Node 26.5 — optional dot/bracket/runtime-key forms, class & object-literal computed method keys, non-well-known keys →undefined, localSymbolshadowing, and nullish-receiver short-circuit.rustfmt --checkclean; file-size gate green (expr_member.rsallowlisted).Version bump + changelog intentionally omitted — maintainer folds those in at merge (external-contributor flow).
Summary by CodeRabbit
Bug Fixes
Symbolproperties (including dot and computed forms) so they resolve correctly instead of falling back to generic property access.Symbolshadowing is respected and optional chaining still short-circuits fornull/undefinedreceivers.Tests
Symbolaccess, literal vs runtime keys, shadowedSymbol, and null-receiver short-circuiting.