fix(hir): a nested class's own name beats an enclosing binding in new - #8153
Conversation
23f70d1 to
8822bf3
Compare
|
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 (5)
Included review availability: Your plan includes up to 8 reviews per rolling hour; 7 remain after this review. 📝 WalkthroughWalkthroughNested class-name resolution now compares class and local declaration depth. Identifier and ChangesNested class shadowing
Possibly related PRs
Suggested reviewers: Merge Risk: ⚪ Minimal · up to This localized fix aligns nested-class construction with JavaScript name resolution and includes targeted regression tests; no actionable merge-blocking risk remains after normal checks and review. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
📝 Generate docstrings
🧪 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 |
End-to-end on the #8040 fixtureMeasured on
So the tracer failure this PR targets is gone from the real app. What is behind it is a different defect, not a variant of this one:
Neither reproduces in the otel harness this PR is validated against; they want their own issue. |
cbd667a to
b1a1495
Compare
`class C` declared inside a nested function, constructed by `new C()` from
one of its own method bodies, threw `TypeError: undefined is not a
constructor` whenever an enclosing scope also declared `var C` / `let C`.
The bare-ident read arm already applied the JS nearest-binding rule, so a
plain `C` in the same method resolved to the class. The `new <Ident>` arm
did not: it snapshotted `lookup_local("C")` unconditionally and rerouted the
construct to `NewDynamic { LocalGet(<outer slot>) }`. A method compiles to
its own function, where that slot index names an unrelated uninitialized
local, so the callee evaluated to `undefined`.
Extract the rule into `LoweringContext::forward_class_shadows_local` and use
it from both arms so they cannot drift again. The depth half of the rule
keeps the case the reroute exists for: a module-scope `class e` still loses
to a factory-local `let e`.
Next 16's webpack chunk for the bundled `@opentelemetry/api` is this shape,
which is why a production App Route could not serve a request (#8040).
The collision rename accidentally immunises every duplicate single-letter class, so only the first `class <letter>` of a name reaches the reroute. That asymmetry is why the bundled @opentelemetry/api lost `context` and `propagation` but kept `trace`, and why the symptom moves when unrelated code is added to the file. Add that shape plus an over-trigger guard for a method-scope local named after its own class.
b1a1495 to
72c8b74
Compare
Refs #8040.
What breaks
A
class Cdeclared inside a nested function, constructed bynew C()from one of its own method bodies, throwsTypeError: undefined is not a constructorwhenever some enclosing scope also declares a binding namedC. Node runs it — the class's own name binding is the nearest one.Root cause
Two arms of ident lowering disagreed about the same name.
The bare-ident read arm (
arm_ident.rs) already applied the JS nearest-binding rule throughforward_class_names+forward_class_decl_depth, so a plainAinside the method resolved toClassRef("A")—typeof Ain that same method returns"function".The
new <Ident>arm did not. It snapshottedctx.lookup_local("A")unconditionally, found the enclosing scope's binding, and rerouted the construct toNewDynamic { callee: LocalGet(<outer slot>) }. A method compiles to its own function, so that slot index names an unrelated, uninitialized local there; the callee evaluates toundefinedand the construct throws.Everything up to that point is silent: the class registers, its methods exist, and every reference to the name other than
newresolves correctly.The fix
Extract the rule into
LoweringContext::forward_class_shadows_localand call it from both arms so they cannot drift again:varis nearer than any class);The depth half is what preserves the case the reroute exists for: a module-scope
class estill loses to a factory-locallet e, so mysql2's bundled chunk keeps constructing the local's value. That direction has its own test in this PR.Why it matters
Next 16 ships exactly this shape in the webpack chunk that inlines
@opentelemetry/api: the module IIFE declaresvar g,h,i,j,…and an inner factory declaresgetInstance()threw, so the module factory aborted mid-initialization; webpack's module cache then handed the tracer a{}for@opentelemetry/api, andcontextnever got itsactive().Validation
cargo test -p perry-hir— 312 lib tests plus every integration suite, all green, exit 0.Sabotage: with the new guard forced off,
nested_class_shadowing_outer_var_constructs_the_class_not_the_localfails printing the exact defect:The companion test passes either way by design — it exists to catch over-triggering, not to detect this bug.
cargo fmt --all -- --checkandscripts/check_file_size.shclean.Summary by CodeRabbit
Bug Fixes
newexpressions resolve to the nearest valid class or local binding.Tests