Skip to content

feat: support getters and setters in a class body - #60

Merged
marevol merged 1 commit into
es6/fix-const-pattern-defaultfrom
es6/class-accessors
Aug 28, 2026
Merged

feat: support getters and setters in a class body#60
marevol merged 1 commit into
es6/fix-const-pattern-defaultfrom
es6/class-accessors

Conversation

@marevol

@marevol marevol commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Stacked on #59.

Object literals took accessors and classes did not, so the class form was a syntax error:

class C { get g() { return 2; } }          // Expected ( but found g
class C { set s(v) { this._v = v; } }      // Expected ( but found s
class C { static get g() { return 3; } }   // Expected ( but found g

Plain and static methods already worked; only the accessor form was missing.

Parsing

The member loop now recognises get and set the way an object literal does, and reuses propertyGetterFunction and propertySetterFunction to read them, so the parsing rules — property name, empty getter parameter list, sloppy setter parameter — are the same in both places.

get and set stay usable as ordinary member names. What follows the keyword decides, as it does in an object literal: a property name makes it an accessor, a parameter list makes it a method. class C { get() {} } and static get() {} both still parse as methods.

Lowering

A method lowers to an assignment on the prototype, or on the class itself when static. An accessor is not expressible as an assignment, and unlike an object literal member there is no literal here to carry it, so it lowers to Object.defineProperty on the same target, with configurable: true and enumerable: false.

Defining one at a time is correct for a get/set pair: a descriptor only changes the fields it names, so a setter defined afterwards keeps the getter beside it.

Two things worth recording

Object.defineProperty is read from the global scope, so a class body evaluated where Object is shadowed would not find it. That is the same shape every ES6→ES5 lowering of accessors takes, and it adds no runtime helper.

An accessor is correctly non-enumerable. A method, on the prototype by plain assignment, is still enumerable, which ES6 says it should not be. That is pre-existing and not changed here.

Verification

./gradlew build testOptimistic testPessimistic

suite before after
test 666, 0 fail 666, 0 fail
testOptimistic 1718, 0 fail 1718, 0 fail
testPessimistic 1718, 0 fail 1718, 0 fail

Object literals took accessors and classes did not, so the class form was a
syntax error:

    class C { get g() { return 2; } }          Expected ( but found g
    class C { set s(v) { this._v = v; } }      Expected ( but found s
    class C { static get g() { return 3; } }   Expected ( but found g

Plain and static methods already worked; only the accessor form was missing.

The member loop now recognises get and set the way an object literal does, and
reuses propertyGetterFunction and propertySetterFunction to read them, so the
parsing rules - property name, empty getter parameter list, sloppy setter
parameter - are the same in both places.

A method lowers to an assignment on the prototype, or on the class itself when
static. An accessor is not expressible as an assignment, and unlike an object
literal member there is no literal here to carry it, so it lowers to
Object.defineProperty on the same target, with configurable true and enumerable
false. Defining one at a time is correct for a get/set pair: a descriptor only
changes the fields it names, so a setter defined afterwards keeps the getter
beside it.

"get" and "set" stay usable as ordinary member names. What follows the keyword
decides, as it does in an object literal: a property name makes it an accessor,
a parameter list makes it a method. class C { get() {} } and static get() {}
both still parse as methods.

Two things worth recording:

Object.defineProperty is read from the global scope, so a class body evaluated
where Object is shadowed would not find it. That is the same shape every ES6 to
ES5 lowering of accessors takes, and it adds no runtime helper.

An accessor is correctly non-enumerable. A method, on the prototype by plain
assignment, is still enumerable, which ES6 says it should not be. That is
pre-existing and not changed here.

./gradlew build testOptimistic testPessimistic:

  suite            before         after
  test             666, 0 fail    666, 0 fail
  testOptimistic   1718, 0 fail   1718, 0 fail
  testPessimistic  1718, 0 fail   1718, 0 fail
@marevol
marevol force-pushed the es6/class-accessors branch from bc0e581 to 284d022 Compare August 28, 2026 05:11
@marevol marevol self-assigned this Aug 28, 2026
@marevol
marevol merged commit 15b4a27 into master Aug 28, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant