Created
March 27, 2022 18:17
Revisions
-
pzuraq renamed this gist
Mar 27, 2022 . 1 changed file with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes. -
pzuraq created this gist
Mar 27, 2022 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,193 @@ # Class Element Definitions **Stage**: 0 Class element definitions (CEDs) are a proposal for extending JavaScript classes with a syntax that mirrors the behavior of `Object.defineProperty`. ```js class C { x { configurable = true; enumerable = true; writable = true; value() { console.log('hello!'); } } y { get; set; } = 123; } ``` This would enable: 1. Changing the `enumerable`, `writable`, and `configurable` properties of class elements in a declarative manner. 2. Grouping related element definitions, such as getters and setters, in a single location. 3. Automatic definitions for getters and setters, which simplifies [common decoration use cases](https://github.com/tc39/proposal-decorators#class-accessors). 4. Definition of non-method values on class prototypes. ## Motivation Currently, there are a number of portions of the JavaScript object model which are not easily accessible via class syntax. For instance: - It is not possible to declaratively define a class field which is non-enumerable. - It is not possible to declaratively define a non-method value which is assigned to the prototype of the class rather than the constructor or the instance. - It is not possible to declaratively make a method non-configurable. All of these use cases can be accomplished imperatively using `Object.defineProperty` after the class has been defined, but it has been a longstanding goal to add a way for class syntax to accomplish these use cases and covers these gaps in mapping from class model to object model. Originally, it was believed that [decorators](https://github.com/tc39/proposal-decorators) would be able to solve these use cases, and earlier versions of that proposal _did_ solve them. However, it was determined that these capabilities were too dynamic - they would fundamentally require class definitions to be far more dynamic and less optimizable. As such, decorators no longer can change the enumerability, writability, or configurability of a class element, and so we would need a new language feature to do this. This new feature also needs to be _statically analyzable_ so that the changes to the shape of the class can be determined at parse time. CEDs provide this syntax, and also provide a convenient way to group related definitions (such as getters and setters on the same property name) in a single location. In addition, CEDs provide a way to create automatic accessors, which are useful for a variety of decoration use cases. ## Detailed Design The syntax for CEDs is an identifier followed by a block in a class body (i.e. `Identifier {...}`). This block may contain field assignments or method definitions for the following properties: - `writable` - MUST be a class field - `enumerable` - MUST be a class field - `configurable` - MUST be a class field - `value` - can be a class field or a class method. CANNOT be an empty field. - `get` - can be a class field or a class method - `set` - can be a class field or a class method As such, it is a strict subset of the syntax of a class body. For example, to define a non-writable class field, you would do: ```js class C { x { writable = false }; } ``` The values of the CED block are 1-to-1 with the options provided to `Object.defineProperty`, and generally have the same meaning and effect. Restrictions are also the same, for instance it would be a syntax error to have both `value` and `get` or `set` in the CED, since that is an invalid combination. The shape of the CED block is approximately the following: ```ts class C { identifier { writable?: boolean; enumerable?: boolean; configurable?: boolean; value?: unknown; get?: () => T; set?: (v: T) => void; }: T; } ``` ### Defining prototype values The `value` property of the CED maps to the value defined on the prototype (for non-static CEDs). Essentially, the following two definitions have the same semantics: ```js class C { m() {} m { value() {} }; } ``` Unlike method syntax, however, `value` can be assigned any value and it will still be assigned to the prototype: ```js class C { m { value = 123 }; } C.prototype.m; // 123 ``` ### Auto-Accessors A common use case for meta-programming and decoration is to intercept access to a property and add functionality. This can be used for instance to add _reactivity_ to a property. As part of this proposal, providing an empty `get` or `set` value will instead generate a default accessor which accesses a backing storage property, similar to [auto-implemented properties in C#](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/auto-implemented-properties). ```js class C { x { get; set; } = 123; } ``` This syntax could be approximately implemented (e.g. polyfilled) like so: ```js class C { #x = 123; get x() { return this.#x; } set x(v) { this.#x = v; } } ``` This getter and setter can then be replaced (for instance via a decorator), while keeping the backing storage slot which contains the state of the field. In order to avoid confusion, `get` and `set` are the only auto-implemented values. `value` would _require_ some value or implementation to be assigned to it, even if that value is undefined: ```js class C { x { value; } // Syntax error: value must have a value assigned to it x { value = undefined; } // Valid, makes C.prototype.x === undefined } ``` ### Valid and Invalid Combinations CEDs can be used with fields _or_ prototype values. Where the value exists depends on what values are included in the CED. Here are some examples of what ends up as a class field and what ends up as a method, and what combinations are invalid ```js class C { // fields, on instance x { writable = true }; x { enumerable = true }; x { configurable = true }; // fields with initializers, on instance x { writable = true } = 123; x { enumerable = true } = 123; x { configurable = true } = 123; // auto-accessors, on both instance and prototype x { get; }; x { set; }; x { get; } = 123; x { set; } = 123; // methods, on prototype x { value() {} }; x { get() {} }; x { set() {} }; x { get = someGetFn }; x { set = someSetFn }; // invalid combinations/syntax errors x { value() {} } = 123; // Cannot have both a value and initializer x { get() {} } = 123; // Can only have empty/auto get if you have initializer x { set() {} } = 123; // Can only have empty/auto set if you have initializer x { get = someGetFn } = 123; // Can only have empty/auto get if you have initializer x { set = someSetFn } = 123; // Can only have empty/auto set if you have initializer } ``` ## Alternatives ### Syntactic Opt-In The proposed syntax would carve out an entire syntactic space (i.e., `Identifier {...}`) that could prevent future exploration of syntax in this space. For instance, `static {}` has already been added in this space (and would prevent a CED named `static` from ever being defined), and its certainly possible that future extensions and features could also come up. One way we could get around this is with a more explicit syntactic opt-in, either via a keyword before the CED or some alternative syntax for the CED block which distinguishes it. Some ideas: ```js class C { // `define` keyword define x { writable = false } = 123; @reactive define x { get; set; } = 123; // `def` keyword def x { writable = false } = 123; @reactive def x { get; set; } = 123; } ```