Skip to content

fix(parser): allow accessor keyword with experimentalDecorators: true - #27336

Closed
robobun wants to merge 1 commit into
mainfrom
claude/fix-accessor-experimental-decorators
Closed

fix(parser): allow accessor keyword with experimentalDecorators: true#27336
robobun wants to merge 1 commit into
mainfrom
claude/fix-accessor-experimental-decorators

Conversation

@robobun

@robobun robobun commented Feb 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Fixes the accessor keyword (TC39 auto-accessors proposal) being rejected as a syntax error when experimentalDecorators: true is set in tsconfig.json
  • The accessor keyword was incorrectly gated behind the standard_decorators feature flag in the parser, but TypeScript supports it in both decorator modes
  • Auto-accessors now always trigger the standard lowering path (WeakMap + getter/setter) regardless of decorator mode

Closes #27335

Test plan

  • New regression tests in test/regression/issue/27335.test.ts covering:
    • Basic accessor with experimentalDecorators: true
    • accessor with public, private, static modifiers + experimentalDecorators: true
    • accessor without experimentalDecorators (standard mode, existing behavior)
    • Mixed: accessor fields + experimental decorators on other class members
  • All tests fail with system bun (USE_SYSTEM_BUN=1), pass with debug build
  • All existing decorator test suites pass:
    • es-decorators.test.ts (27 pass)
    • es-decorators-esbuild.test.ts (147 pass)
    • decorators.test.ts (22 pass)
    • decorator-metadata.test.ts (5 pass)

🤖 Generated with Claude Code

…rue`

The `accessor` keyword (TC39 auto-accessors proposal) was incorrectly
gated behind the `standard_decorators` feature flag, causing it to be
rejected as a syntax error when `experimentalDecorators: true` was set
in tsconfig.json. TypeScript supports `accessor` in both decorator modes.

Two changes:
- Remove `standard_decorators` guard from accessor keyword recognition
  in the parser so it's always recognized in class bodies.
- Set `should_lower_standard_decorators` when auto-accessors are present
  regardless of the decorator mode, since auto-accessors always need the
  standard lowering path (WeakMap + getter/setter transformation).

Closes #27335

Co-Authored-By: Claude <noreply@anthropic.com>
@robobun

robobun commented Feb 21, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 10:48 PM PT - Feb 21st, 2026

❌ Your commit b79bef89 has 5 failures in Build #37890 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 27336

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

bun-27336 --bun

@coderabbitai

coderabbitai Bot commented Feb 21, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉


Walkthrough

Support for TypeScript's auto-accessor keyword (stage 3 ECMAScript proposal) is now recognized unconditionally within classes. The parser's decorator lowering logic was adjusted, and comprehensive regression tests were added to validate accessor behavior across various decorator configurations.

Changes

Cohort / File(s) Summary
Decorator and Accessor Parsing
src/ast/parse.zig, src/ast/parseProperty.zig
Modified condition for standard decorator lowering to prioritize auto-accessor detection. Removed the standard\_decorators feature flag requirement for recognizing the "accessor" keyword in class properties, allowing it to be recognized unconditionally.
Regression Tests
test/regression/issue/27335.test.ts
Added comprehensive regression test suite validating the TypeScript accessor keyword behavior in classes across different decorator modes (experimentalDecorators enabled, disabled, or unspecified), including scenarios with multiple modifiers, static members, and decorator interactions.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: fixing the parser to allow the accessor keyword when experimentalDecorators: true is set.
Description check ✅ Passed The PR description comprehensively covers the issue, root cause, solution, and includes detailed test coverage information demonstrating thorough validation.
Linked Issues check ✅ Passed The code changes fully address the objectives from issue #27335: allow the accessor keyword to parse correctly when experimentalDecorators: true, support it consistent with TypeScript, and include regression tests.
Out of Scope Changes check ✅ Passed All three modified files are directly related to the PR objective: parser changes for accessor keyword support and regression test coverage for the fix.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@claude

claude Bot commented Feb 21, 2026

Copy link
Copy Markdown
Contributor

🔴 b79be — 1 issue(s) found

Issue Severity File
Mixed accessor + experimental decorators uses wrong lowering path 🔴 Critical src/ast/parse.zig

Comment thread src/ast/parse.zig
.properties = properties.items,
.has_decorators = has_any_decorators,
.should_lower_standard_decorators = p.options.features.standard_decorators and (has_any_decorators or has_auto_accessor),
.should_lower_standard_decorators = has_auto_accessor or (p.options.features.standard_decorators and has_any_decorators),

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.

🔴 Critical: Bug: when experimentalDecorators: true and a class has an accessor field, should_lower_standard_decorators is set to true because has_auto_accessor is now evaluated before the standard_decorators guard. This routes the entire class (including any legacy experimental decorators on other members) through the TC39 standard decorator lowering path (lowerStandardDecoratorsStmt / __decorateElement), which calls decorators with (value, context) instead of the expected experimental signature (target, key, descriptor). The fix should gate has_auto_accessor on standard_decorators as well, or handle accessor lowering separately from the decorator lowering path when in experimental mode.

Why this is a problem

What the Bug Is

The PR changes the assignment of should_lower_standard_decorators from:

p.options.features.standard_decorators and (has_any_decorators or has_auto_accessor)

to:

has_auto_accessor or (p.options.features.standard_decorators and has_any_decorators)

This boolean logic change means that the presence of an accessor field alone is now sufficient to set should_lower_standard_decorators = true, regardless of whether the file is using standard or experimental decorators. In the old code, standard_decorators was a prerequisite for the entire expression, ensuring experimental decorator mode could never reach the standard lowering path. In the new code, has_auto_accessor short-circuits past that guard.

The Specific Code Path

When experimentalDecorators: true is set in a TypeScript project's tsconfig.json, the transpiler sets opts.features.standard_decorators = false (confirmed at src/transpiler.zig:1106 and src/bundler/ParseTask.zig:1217). Under these conditions, a class like:

class Foo {
  accessor x = 1;
  @log greet() { return "hello"; }
}

will have has_auto_accessor = true (from the accessor x field) and has_any_decorators = true (from the @log decorator). With the new code, should_lower_standard_decorators evaluates to true or (false and true) = true. In P.zig lowerClass (line 4873), when should_lower_standard_decorators is true, the class is immediately routed to lowerStandardDecoratorsStmt which returns, completely bypassing the legacy experimental decorator path starting at line 4884.

Why Existing Tests Don't Catch It

The PR includes a test case (test case 4: "accessor with experimental decorators on other members") that appears to validate this scenario, but the @log decorator used in that test is a no-op: function log(target: any, key: string) {}. Since it discards its arguments, it produces no observable difference regardless of whether it's called with (value, context) (standard TC39 protocol) or (target, key, descriptor) (experimental protocol). A real-world experimental decorator that inspects or modifies target, key, or descriptor would silently receive incorrect arguments.

Step-by-Step Proof

  1. User has experimentalDecorators: true in tsconfig.json, so standard_decorators = false.
  2. User writes a class with both accessor x = 1 and @myDecorator greet() {}, where myDecorator is an experimental-style decorator expecting (target, key, descriptor).
  3. The parser sets has_auto_accessor = true and has_any_decorators = true.
  4. Line 212 evaluates: should_lower_standard_decorators = true or (false and true) = true.
  5. In P.zig lowerClass (line 4873), the condition stmt.data.s_class.class.should_lower_standard_decorators is true.
  6. lowerStandardDecoratorsStmt is called, which processes ALL decorators in the class using __decorateElement (in lowerDecorators.zig), calling each decorator with the TC39 standard protocol (value, context).
  7. @myDecorator receives (value, context) instead of (target, key, descriptor). The target parameter gets a function value instead of the class prototype, key gets a context object instead of the method name string, and descriptor is undefined.
  8. The decorator silently produces incorrect behavior or throws a runtime error.

How to Fix It

The fix should ensure that has_auto_accessor only triggers the standard decorator lowering path when standard_decorators is also true. One approach is:

.should_lower_standard_decorators = p.options.features.standard_decorators and (has_any_decorators or has_auto_accessor),

(i.e., reverting to the original logic) and handling the accessor keyword lowering for experimental decorator mode through a separate mechanism. Alternatively, if the intent is to support accessor with experimental decorators, the accessor field should be lowered independently of the decorator lowering path, perhaps by transforming it into a getter/setter pair without routing through lowerStandardDecoratorsStmt.

@robobun

robobun commented Apr 11, 2026

Copy link
Copy Markdown
Collaborator Author

Superseded by #29201 — same root cause, with a proper accessor + legacy decorator combination fix (desugars to #storage + get/set pair instead of flipping to standard-decorator lowering, which would break @legacyDec accessor x).

@robobun robobun closed this Apr 11, 2026
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.

TypeScript accessor keyword in classes fails to parse

1 participant