Skip to content

Rewrite super property accesses in private methods extracted by decorator lowering - #38769

Open
robobun wants to merge 6 commits into
mainfrom
farm/bc9f08f4/decorator-private-method-super
Open

robobun wants to merge 6 commits into
mainfrom
farm/bc9f08f4/decorator-private-method-super

Conversation

@robobun

@robobun robobun commented Aug 15, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • With standard (TC39) decorators, a class that has a decorated member and a private method, getter or setter using super.x fails to load: SyntaxError: super is not valid in this context. Decorated private methods (@dec #m() { return super.x }) and static private methods fail the same way.
  • Cause: lower_impl in src/js_parser/lower/lower_decorators.rs moves private method bodies out of the class body. Undecorated ones become _m_fn = function () { ... } emitted before the class (the lower_all_private path), decorated ones are passed to __decorateElement as a function argument. The function value was copied verbatim, and super.x is only valid inside a class body or object literal method, so the printed module is a syntax error (bun build --no-bundle shows _im_fn = function() { return super.greet(); } at module level).
function dec(v, c) { return v; }
class Base { greet() { return "hi"; } }
class C extends Base {
  @dec m() {}
  #im() { return super.greet(); }
  callI() { return this.#im(); }
}
console.log(new C().callI()); // bun 1.4: SyntaxError; tsc, esbuild, native decorators: hi

Fix

  • Adds a RewriteKind::LowerSuper pass to the relocation rewriter already used for this in extracted static blocks, and runs it over the parameters and body of every function the two extraction sites take out of the class:
    • super.x, super[k] in value position become __superGet(home, this, key)
    • super.f(...) becomes __superGet(...).call(this, ...); super.f?.(...) keeps its short-circuit as ?.call(this, ...)
    • super.x = v becomes __superSet(home, this, key, v) (evaluates to v)
    • every other assignable use (+= and the logical assignments, ++/--, destructuring assignment targets, for (super.x of ...) heads) becomes __superWrapper(home, this, key)._, an object whose _ accessor reads and writes through the two helpers. The operator itself is left in place, so it keeps its native semantics (ToNumeric, BigInt, return value, short-circuiting) and the key expression is evaluated exactly once without temporaries
    • super.tag`...` becomes __superGet(...).bind(this)`...`
    • delete super.x becomes __superDelete(key), which evaluates the key and then throws a ReferenceError without reading the property, as the native form does
  • home is a new temporary holding the class, bound by static { _home = this; } emitted as the first element of the class body, and used as _home for static members and _home.prototype for instance members. Both the temporary and the block are only emitted when a lowered body actually contains a super access.
  • Why this is correct: per spec, super.x in a method means Reflect.get(HomeObject.[[GetPrototypeOf]](), "x", this), where HomeObject is the class for static members and C.prototype otherwise; __superGet(home, obj, key) is Reflect.get(Object.getPrototypeOf(home), key, obj), i.e. the same operation with the same receiver, so inherited getters, setters and methods see the right this, and super.x = v with no inherited setter defines the property on the instance like native code does. These are the helpers esbuild uses for the same lowering. __superSet throws when Reflect.set reports failure because class bodies are strict code, where the native assignment throws too.
  • Why the class is captured from inside the body instead of using the class binding (esbuild's __superGet(C.prototype, ...)) or the _base temporary: class decorators reassign the binding, and with a replacing decorator (return class extends cls {...}) a lookup through the replacement's prototype finds the original class's own methods instead of the parent's (esbuild 0.25 has this bug); the binding is also in its TDZ while the body's static initializers run. Object.getPrototypeOf(_home) instead follows the class's actual prototype chain at access time, also covers classes without extends (Object.prototype / Function.prototype) and later Object.setPrototypeOf re-parenting without special cases, and the leading static block makes it available to static initializers that call a lowered static method while the class is still being defined. _home is declared the same way as the _m_fn temporaries that reference it, so it has exactly their lifetime.
  • The walk enters arrow functions, parameter defaults, destructuring defaults, computed keys, await/yield/import() operands and the extends clause and computed keys of nested classes, and stops at non-arrow functions, object literal methods and the members of nested classes, which have their own super. The traversal positions added to the shared walker (rewrite_args, rewrite_binding, rewrite_class, for-in/of heads, catch bindings) apply to the existing ReplaceThis/ReplaceRef kinds too, which previously skipped them.
  • __superGet, __superSet, __superWrapper and __superDelete are added to src/runtime.js and to the bun:wrap import table in src/ast/runtime.rs (appended, so existing indices are unchanged).
  • html-import-manifest.test.ts, bundler_promiseall_deadcode.test.ts and cyclic-imports-async-bundler.test.js pin output hashes, etags and debug ids. A chunk's isolated hash includes the part ranges of every file in the chunk, the runtime module among them (LinkerContext::generate_isolated_hash), so the statements added to runtime.js move those values even though the emitted code is byte-for-byte unchanged (checked by diffing the client bundle of the manifest test against the released binary's). Earlier runtime.js additions needed the same updates (feat(transpiler): implement TC39 standard ES decorators lowering #26436, Reduce the number of closures in generated bundler code #27022).
  • Verification:
    • test/bundler/transpiler/es-decorators.test.ts, new super property access in lowered private methods block (11 tests): the repro, decorated private method/getter/setter/static method, no extends clause, a class that runs the same members natively and lowered and asserts identical output for every access form above, delete included (that program also prints the same thing under node with the decorator removed), tag receiver, class decorator replacing a declaration and an expression, a static initializer calling a lowered static method during class definition, per-evaluation classes in a function, a nested class keeping its own super, and the emitted shape (capture present only when used, in-body methods untouched). All 11 fail on the released binary with the SyntaxError above and pass with this change.
    • es-decorators-esbuild (esbuild's 147 conformance tests), decorators, decorator-metadata, bundler_decorator_metadata, ts-use-define-for-class-fields, bundler_edgecase, bundler_regressions, bundler_minify, bundler_cjs, esbuild/{ts,lower,default}, transpiler.test.js, regression/issue/27575, test/internal/source-lints: pass.
    • The access-form program produces identical output through bun run, bun build, --minify, --target=bun, --format=cjs and --format=iife; cargo clippy -p bun_js_parser -p bun_ast is clean.
  • Scope and landing order: lower_impl relocates code out of the class body at five places. This PR adds the mechanism and applies it to the two that extract private method bodies. Lower super in static blocks and static initializers relocated by decorator lowering (stacked on #38769) #38730 is now stacked on this branch and applies the same SuperLowering (followed by the existing ReplaceThis) to the other three, relocated static blocks and static field/accessor initializers, with its own tests; it should land after this one. The equivalent site in the legacy experimentalDecorators lowering (p.rs, decorated static members moved after the class) has the same defect and is tracked separately. Give decorator lowering temporaries file-unique names #31930 gives lowering temporaries unique names; _home is created like the temporaries it is used alongside and is covered by that change the same way. Substitute this and inner class name in relocated static initializers during decorator lowering #31922 and Let a decorated class body observe the class its decorators return #38731 touch the same function (textual overlap only).

Background

  • Standard decorator lowering: a class with TC39 decorators is printed as a plain class followed by __decorateElement(...) calls that apply the decorators. Private members cannot be reached by code emitted outside the class body, so once any member is decorated, every private member is lowered to WeakSet/WeakMap storage (lower_all_private), and private method bodies become module-level function expressions that the in-body code calls through __privateMethod(this, _m, _m_fn).call(this).
  • [[HomeObject]]: every method records the object it was defined on (the class for static methods, C.prototype otherwise). super.x looks x up on that object's prototype, at access time, and uses the method's own this as the receiver for getters, setters and the call. Reflect.get / Reflect.set are the built-ins that take an explicit receiver, which is why the helpers are written in terms of them.
  • The relocation rewriter (rewrite_expr / rewrite_stmts, parameterized by RewriteKind) is the lowering's walker for code it moves somewhere this or super no longer means the same thing; ReplaceThis is what relocated static blocks already used.
  • bun:wrap is the module the transpiler imports runtime helpers from (src/runtime.js, bundled into the runtime module for bun build); Imports::ALL in src/ast/runtime.rs is the list of names call_runtime is allowed to emit, so new helpers have to be registered there as well.
Emitted code for the repro
var _home;
var _im = new WeakSet, _im_fn;
_im_fn = function() {
  return __superGet(_home.prototype, this, "greet").call(this);
};
var _init = __decoratorStart(_base);
class C extends _base {
  static {
    _home = this;
  }
  constructor() {
    super(...arguments);
    __privateAdd(this, _im);
    __runInitializers(_init, 5, this);
  }
  m() {}
  callI() {
    return __privateMethod(this, _im, _im_fn).call(this);
  }
}
__decorateElement(_init, 1, "m", _dec, C);
__decoratorMetadata(_init, C);

Other forms, from the access-form test:

__superSet(_home.prototype, this, "x", 1);
__superWrapper(_home.prototype, this, "x")._ += 10;
__superWrapper(_home.prototype, this, key)._++;
[__superWrapper(_home.prototype, this, "x")._, __superWrapper(_home.prototype, this, key)._] = [100, 200];
for (__superWrapper(_home.prototype, this, "x")._ of [31, 32]) ...
__superGet(_home.prototype, this, "f")?.call(this, 5);
__superGet(_home.prototype, this, "tag").bind(this)`a${1}b${2}`;
__superDelete("probe");                           // delete super.probe
__superGet(_home, this, "sf").call(this, "a");   // static member

@coderabbitai

coderabbitai Bot commented Aug 15, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 4 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a7b5c724-0178-4146-8d90-3d68001acc47

📥 Commits

Reviewing files that changed from the base of the PR and between fd9a307 and 375ad3e.

📒 Files selected for processing (7)
  • src/ast/runtime.rs
  • src/js_parser/lower/lower_decorators.rs
  • src/runtime.js
  • test/bundler/bundler_promiseall_deadcode.test.ts
  • test/bundler/html-import-manifest.test.ts
  • test/bundler/transpiler/es-decorators.test.ts
  • test/regression/issue/cyclic-imports-async-bundler.test.js

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

@robobun

robobun commented Aug 15, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status: ready for review.

Reproduced on bun 1.4.0 with the class in the PR description (SyntaxError: super is not valid in this context.); bun build --no-bundle shows the private method body emitted as a module-level function containing super.greet(). With this branch it prints hi, and the super property access in lowered private methods block in test/bundler/transpiler/es-decorators.test.ts (11 tests) fails on the released binary and passes here.

Since opening: delete super.x now lowers to a throwing helper like the native form (review finding), the three bundler tests that pin chunk hashes were updated because the runtime module gained statements (explained in the description), and the code comments were shortened. #38730 has been rebased onto this branch and reuses the mechanism for relocated static blocks and static initializers, so the intended order is this PR first, then #38730.

CI: the new tests and every suite touched by this change pass on all lanes in the last two builds (97028, 97301). The remaining red entries are pre-existing intermittent failures unrelated to this diff and already reported separately (test-http-chunk-problem.js ASAN use-after-free, and the html-rewriter-leak / timers/setInterval leak-test timeouts on the ASAN lane); the rest passed on retry. Not re-running CI further; this is ready for a maintainer.

…ator lowering

Standard decorator lowering moves private methods, getters and setters out
of the class body into plain function expressions once the class has a
decorated member. A super property access in one of those bodies was copied
verbatim, and super is a syntax error outside a class body, so the whole
module failed to load.

The lowering now rewrites those accesses before extracting the body:
reads become __superGet(home, this, key), calls keep their receiver through
.call(this) (optional calls through ?.call), plain assignments become
__superSet, and every other assignable use (compound and logical
assignment, ++/--, destructuring targets, for-in/of heads) goes through the
accessor returned by __superWrapper so the operator keeps its native
semantics. A tagged template binds the receiver. The walk stops at
non-arrow functions and at the members of nested classes, which have their
own super.

home is the original class, captured into a temporary by a static block
emitted as the first element of the class body (or its prototype for
instance members), so the lookup starts at the method's [[HomeObject]] even
when a class decorator later replaces the class, and it is available to
static initializers that call the method while the class is still being
defined. The temporary and the static block are only emitted when a lowered
body actually uses super.

The three helpers are added to runtime.js and to the bun:wrap import table.
@robobun
robobun force-pushed the farm/bc9f08f4/decorator-private-method-super branch from 4703d3c to f2ebbb2 Compare August 15, 2026 00:37
@robobun

robobun commented Aug 15, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 11:05 PM PT - Aug 14th, 2026

❌ @robobun, your commit 375ad3e has 1 failures in Build #97301 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 38769

That installs a local version of the PR into your bun-38769 executable, so you can run:

bun-38769 --bun

Comment thread src/js_parser/lower/lower_decorators.rs Outdated
Comment thread src/js_parser/lower/lower_decorators.rs Outdated
Comment thread src/js_parser/lower/lower_decorators.rs Outdated
Comment thread src/js_parser/lower/lower_decorators.rs Outdated
Comment thread src/js_parser/lower/lower_decorators.rs Outdated
Comment thread src/js_parser/lower/lower_decorators.rs Outdated
Comment thread src/js_parser/lower/lower_decorators.rs Outdated
Comment thread src/js_parser/lower/lower_decorators.rs Outdated
Comment thread src/js_parser/lower/lower_decorators.rs Outdated
Comment thread src/js_parser/lower/lower_decorators.rs Outdated
Comment thread src/js_parser/lower/lower_decorators.rs Outdated
Comment thread src/js_parser/lower/lower_decorators.rs Outdated
Comment thread src/js_parser/lower/lower_decorators.rs Outdated
Comment thread src/runtime.js Outdated
A chunk's isolated hash covers the part ranges of every file in the chunk,
including the runtime module, so the statements added to runtime.js move
the hashed output names, etags and debug ids these tests pin even though
the emitted code is unchanged.
Comment thread src/js_parser/lower/lower_decorators.rs Outdated
Comment thread src/js_parser/lower/lower_decorators.rs Outdated
Comment thread src/js_parser/lower/lower_decorators.rs
delete of a super reference throws a ReferenceError after evaluating the
key and without reading the property. Routing it through __superGet ran the
inherited getter and returned true instead. The access-form test now covers
it, and the chunk hashes pinned by the bundler tests move again with the
added runtime statement.
Comment thread src/js_parser/lower/lower_decorators.rs Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I reviewed this PR and the earlier delete super.x finding has been addressed; the follow-up run found no further bugs. Given the size of the new lowering pass (~400 lines of AST rewriting covering every super access form, plus new traversal positions that also affect the existing ReplaceThis/ReplaceRef walkers) and its overlap with #38730/#31930/#31922/#38731, a human look is still worthwhile.

Checked and ruled out: object-literal methods and nested class members correctly keep their own super (the EFunction/rewrite_class arms stop the walk; covered by the objectLiteral/nestedClass cases in the access-form test); the Imports::ALL_SORTED/ALL_SORTED_INDEX tables are consistent with the four appended helpers (guarded by the existing all_sorted_matches_zig_comptime test); the for-in/of head rewrite writes through the arena pointer the same way the sibling ECall/EBinary arms do.

Extended reasoning...

Overview

This PR fixes a SyntaxError: super is not valid in this context when standard (TC39) decorator lowering extracts private method/getter/setter bodies out of the class body. It adds a RewriteKind::LowerSuper pass to the existing relocation rewriter in src/js_parser/lower/lower_decorators.rs (~400 new lines), four new runtime helpers (__superGet/__superSet/__superWrapper/__superDelete) in src/runtime.js, registers them in src/ast/runtime.rs, adds 11 new tests (~350 lines) in es-decorators.test.ts, and updates three bundler tests whose pinned chunk hashes moved because runtime.js gained statements.

Security risks

None identified. This is transpiler output-shape work; the new runtime helpers are thin wrappers over Reflect.get/Reflect.set/Object.getPrototypeOf with no untrusted-input parsing, filesystem, network, or auth surface.

Level of scrutiny

High. The change is in a hot transpiler path (decorator lowering runs on every class with a standard decorator), implements spec-mandated semantics across many syntactic forms (value position, call, optional call, simple/compound/logical assignment, pre/post inc/dec, destructuring targets, spread/rest, for-in/of heads, tagged templates, delete), and extends the shared rewrite_expr/rewrite_stmts walker with new traversal positions (rewrite_args, rewrite_binding, rewrite_class, for-head, catch binding) that also apply to the pre-existing ReplaceThis/ReplaceRef kinds. The PR description itself calls out non-obvious design choices (capturing the class via static { _home = this } rather than using the class binding or the _base temporary, following the prototype at access time, ordering the capture block first) and their interaction with class-decorator replacement and TDZ — exactly the kind of decisions a maintainer should sign off on.

Other factors

  • The one issue found in the earlier review pass (delete super.x lowering to delete __superGet(...)) was fixed in 0b1496b with a __superDelete helper and a Native/Lowered comparison test case; that thread is resolved.
  • The comment-cop bot's paragraph-length-comment complaints were addressed in a19bd59/58e857ae (all resolved).
  • Test coverage is thorough: the "every access form behaves like the native class" test runs the same member set through a native and a lowered class and asserts identical JSON output plus a documented literal, which is a strong differential check. The emitted-shape test asserts _home is only introduced when needed and that in-body methods keep native super.
  • Four open PRs (#38730, #38731, #31930, #31922) touch the same file; the description explains the intended interaction, but merge order and folding are a human call.
  • The runtime.rs index tables (ALL, ALL_SORTED, ALL_SORTED_INDEX, field, field_mut) were all updated consistently and are guarded by the existing unit test.

This is well-executed and well-tested, but not the kind of simple/mechanical change the auto-approval bar is meant for.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I re-reviewed after the delete super.x fix in 0b1496b and didn't find any further issues — the __superDelete helper and the added Native/Lowered deleteSuper case look right. Given the scope (~400 lines of new AST-rewriting logic in the transpiler, an extended traversal that also affects the existing ReplaceThis/ReplaceRef passes, and the stated overlap with #38730/#31930/#31922/#38731), a human look would still be worthwhile.

What was reviewed:

  • lower_super_in_expr handling of every access form (get/set/call/optional call/compound and logical assignment/update/destructure target/for-in-of head/tagged template/delete) against the Native/Lowered comparison test.
  • Traversal boundaries: object-literal methods and nested-class members correctly stop the LowerSuper walk (their EFunction value / enter_members = false gates it), while extends and computed keys are entered.
  • _home capture via the leading static { _home = this } block, only emitted when a lowered body uses super; Imports::ALL/ALL_SORTED/ALL_SORTED_INDEX stay consistent (the unit test in runtime.rs covers this).
  • The three pinned-hash test updates are the expected consequence of adding statements to runtime.js.
Extended reasoning...

Overview

This PR fixes a SyntaxError when TC39 standard-decorator lowering extracts a private method (or getter/setter) that uses super.x out of its class body into a plain function expression. It adds a RewriteKind::LowerSuper pass to the existing relocation rewriter in src/js_parser/lower/lower_decorators.rs that rewrites every super access form to calls into four new bun:wrap helpers (__superGet/__superSet/__superWrapper/__superDelete), and captures the original class via a static { _home = this } block. The traversal itself is extended to walk into arrow/function args, binding defaults, computed keys, await/yield/import() operands, for-in/of heads, catch bindings, and nested-class extends/computed keys — extensions that also apply to the pre-existing ReplaceThis/ReplaceRef kinds. src/ast/runtime.rs and src/runtime.js register the four helpers. Eleven new tests in es-decorators.test.ts cover the repro, decorated/undecorated/static variants, no-extends, class-decorator replacement, per-evaluation classes, nested-class scoping, and a comprehensive Native-vs-Lowered access-form comparison. Three bundler tests that pin chunk hashes/etags/debugIds are updated because runtime.js gained statements.

Prior finding addressed

My earlier review flagged that delete super.x fell through to a plain __superGet read (running the inherited getter and returning true) instead of throwing ReferenceError. Commit 0b1496b adds a UnDelete arm that emits __superDelete(key), and the access-form test now asserts ["ReferenceError", 1, 0] (key evaluated once, getter never read) for both the native and lowered classes. Verified in the current diff.

Security risks

None identified. This is a compile-time AST transformation of user source; the runtime helpers are thin wrappers over Reflect.get/Reflect.set/Object.getPrototypeOf and a ReferenceError throw, matching native super semantics. No untrusted-input parsing, auth, crypto, or filesystem paths are involved.

Level of scrutiny

High. This is ~400 lines of new logic in the JavaScript transpiler — a code path every user's decorated class flows through — implementing spec-mandated [[HomeObject]] semantics across a large syntactic surface. The traversal extensions also change what the existing ReplaceThis/ReplaceRef passes visit (parameter defaults, destructuring defaults, computed keys, for-heads, catch bindings), which the PR description says was previously skipped; that widening is plausibly a fix but is a behavior change beyond the headline bug. Correctness here depends on precise AST-shape reasoning (e.g., that object-literal methods and nested-class members are represented as EFunction/gated by enter_members, so the walk stops at their boundary) that benefits from a maintainer familiar with the AST invariants.

Other factors

  • The PR explicitly overlaps with four other open PRs on the same file (#38730 rewrites super in relocated static blocks, #31930 renames lowering temporaries, #31922/#38731 touch lower_impl); merge order and folding LowerSuper into #38730's static-block sites is a coordination question for a human.
  • Test coverage is thorough: the Native-vs-Lowered comparison is a strong invariant test, and the shape-assertion test confirms _home is only emitted when needed and in-body methods keep native super. The two CI failures reported by robobun (setInterval.test.js, html-rewriter-leak.test.ts on x64-asan) are unrelated to this change.
  • The comment-cop bot flags are all resolved (comments were shortened in a19bd59/58e857ae).

Given the size, the spec subtlety, and the cross-PR coordination, I'm deferring rather than approving.

@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

#38730 (the static block / static initializer sites) is now stacked on this branch instead of carrying its own rewrite: it calls this PR's LowerSuper with is_static: true at the three static relocation sites, followed by the existing ReplaceThis, so nothing needs folding in here. One sequencing note from verifying it: with two classes in one file that both use lowered super, the two var _home declarations alias (the first class's bodies read the second class's home), which for the method bodies here means every later call, so #31930 landing first matters for this PR as much as for the follow-up.

@robobun

robobun commented Aug 28, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status against #40833, which rewrites the standard decorator lowering: since e7eeb9e that branch routes super in extracted private methods through __superGet, __superSet and __superWrapper with C.prototype as the home object, and since 9898f5b it keeps the original class in _home, so a class decorator that returns a subclass no longer changes what super resolves to. Of the 11 tests added here, 9 now pass on that branch (reads, calls, optional calls, tagged templates, assignments, compound and logical assignments, updates, destructuring and for-of targets, getter-only TypeError, async and generator methods, static methods, nested classes, per-evaluation parents, the two class decorator cases).

One case still differs from the native class there, and this PR handles it: delete super.x does not throw the ReferenceError the native form throws ("every access form behaves like the native class" fails only on its deleteSuper entry). The other failing test checks the exact output shape of this PR's _home capture.

Leaving this open for the delete case. It needs a rebase onto #40833. If #40833 picks that up first, this PR can be closed.

@robobun

robobun commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator Author

Status against main after #40833 merged (a22b2aa). The merged revision differs from the one my previous status comment measured: it dropped the __superGet, __superSet and __superWrapper helpers.

I ran this PR's test block against a debug build of main at 09bb546. The block is super property access in lowered private methods (11 tests).

  • 8 of the 10 behavioral tests pass. js_parser: lower standard decorators without moving class members #40833 keeps undecorated #private methods, getters and setters in the class body, so super in them runs natively. every access form behaves like the native class passes, so the delete super.x difference from my previous comment is gone. The two class decorator cases, the class inside a function and the nested class pass too.
  • The last test asserts the old output shape (_home, __superGet). It no longer applies.

Two behavioral tests fail on main:

  1. decorated private method, getter, setter and static method: SyntaxError: super is not valid in this context. The body of a decorated #private method, getter or setter still becomes a function expression. I checked each kind alone. @dec #m() { return super.x }, @dec static #m(), @dec get #g() and @dec set #g(v) all fail this way. js_parser: lower standard decorators without moving class members #40833 lists this under "Not in this PR".
  2. a super tag function is called with the method's this: prints [false,"t"], and the test expects [true,"t"]. This is not a lowering problem. The undecorated #im() stays native, and JavaScriptCore calls the tag of super.tag`...` with the parent as this in every class. Bump WebKit (oven-sh/WebKit#438 preview): call the tag of super.tag... with the method's this #38920 (TaggedTemplateNode: call super.tag... and super[key]... with the method's this WebKit#438) has the fix.

So #40833 supersedes part of this PR. The remaining bug on main is super in the body of a decorated #private method, getter or setter (instance or static). The implementation here patches code that #40833 removed, and the branch conflicts. The fix needs to be redone on top of the new lowering. #38730, which was stacked on this branch, is closed as superseded.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant