fix(hir): #5703 — apply default-parameter prologue for class setters - #5744
Conversation
A setter with a defaulted parameter (`set a(_ = expr)`) dropped its
default: `lower_setter_method_with_name` built the `Param` with
`default: None` and emitted no `if (param === undefined) param = <default>`
prologue (only destructuring was handled). So invoking the setter with
`undefined` (`C.a = undefined`) never ran the default expression — test262
`language/expressions/class/scope-static-setter-paramsbody-var-open` left
`probeParams` undefined ("value is not a function").
Fix: mirror the constructor/method path — lower the default via
`get_param_default` in the param loop (BEFORE `define_local`, so the
default's references resolve to the enclosing scope rather than the
param or the body's hoisted `var`s — the separate parameter/body
VariableEnvironment the spec mandates) and prepend
`build_default_param_stmts` to the body. The default's `x` closure then
binds the OUTER `x` ("outside") while the body's `var x = "inside"` closure
binds the body `x`, matching node byte-for-byte. Applies to instance and
static setters alike; affects only setters that actually declare a
default (`build_default_param_stmts` is inert otherwise).
Regression test: class_expr_static_setter_default_param_scope.
|
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)
📝 WalkthroughWalkthrough
Setter default parameter lowering
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 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 |
Summary
Closes 1 of the 11 residual
language/expressions/classfailures tracked in #5703 — the static-setter casescope-static-setter-paramsbody-var-open.js("value is not a function"). While triaging the other 10 I found the ticket's premise is stale/mis-scoped (details below), so this PR ships the one genuinely-tractable, in-theme fix and documents the rest precisely.What this fixes
A class setter with a defaulted parameter (
set a(_ = expr)) silently dropped its default.lower_setter_method_with_namebuilt theParamwithdefault: Noneand emitted noif (param === undefined) param = <default>prologue — only destructuring was handled. SoC.a = undefinednever ran the default expression.Fix mirrors the constructor/method path:
get_param_defaultin the param loop, beforedefine_local, so the default's references resolve to the enclosing scope rather than the param or the body's hoistedvars — the separate parameter/body VariableEnvironment the spec mandates;build_default_param_stmts(¶ms)to the body.The test's
probeParamsclosure (created in the param default) then binds the outerx("outside") while the body'svar x = "inside"closure binds the bodyx— byte-for-byte node parity. Applies to instance and static setters; inert for setters with no declared default.Regression test:
class_expr_static_setter_default_param_scope(all 6 cases inissue_5703_class_expr_static_dflt_paramsgreen).Before / after (
language/expressions/class)node --experimental-strip-types(which is exactly what the self-validating test262 case asserts). No regressions: instance/static setters with and without defaults all match node; the change is gated bybuild_default_param_stmts(inert unless a default is declared).The remaining 10 — NOT a class-expression regression
The other 10 listed cases (
async-gen-method-static/yield-star-{sync,async}-{return,throw},yield-star-next-non-object-ignores-then, and theirelements/async-gen-private-method-statictwins) are not about class-expression default params and not a #5662 regression. They are a generalyield*return/throw/next delegation gap in async generators:language/statements/class/async-gen-method-static), for plain top-levelasync function*, and for instance methods — the class form is irrelevant. (The statement copies are bucketeddiffrather thanruntime-failonly because an async test's failure still exits 0, so the parity gate onexpressions/classis what surfaced them.)yield*-delegating generator resumed via.return(v)/.throw(e)never forwards to the delegated iterator'sreturn/throw. Perry does route.throw()→catch and.return()→finally into linearized states, but for async generators the abrupt-resume path (generator/lower/abrupt.rs::build_async_catch_route_body) hard-codes a{value: undefined, done: false}return and has no continuation loop (throw_continuation = Noneatgenerator/lower.rs, deliberately, "to stay byte-identical"). A desugar that drives the delegation through atry/catchmakes the inner iterator's methods fire in the correct spec order, but the value yielded after the resume is dropped and the loop can't continue.Closing those 10 requires enabling async-generator abrupt-resume continuation (real yielded-value packaging + continuation loop, like sync generators already have) — a substantial, higher-risk core change to the generator state machine, orthogonal to class-expression lowering. Recommend tracking it as its own issue rather than expanding this PR.
Refs #5703.
Summary by CodeRabbit
undefined.