Skip to content

Commit

Permalink
ES private class elements (microsoft#42458)
Browse files Browse the repository at this point in the history
* Added support for private identifier methods.

* Added tests for private methods.

* Added check to only not allow private name method signatures in anything except classes.
Changes objects literal checking to not bail on first private name found in object literal.

* Added private accessors tests

* Transform private methods

Signed-off-by: Kubilay Kahveci <[email protected]>

* Rename shouldTransformPrivateFields

Signed-off-by: Kubilay Kahveci <[email protected]>

* Accept baseline

Signed-off-by: Kubilay Kahveci <[email protected]>

* Use a single WeakSet for brand-check

Signed-off-by: Kubilay Kahveci <[email protected]>

* Accept baseline

Signed-off-by: Kubilay Kahveci <[email protected]>

* Add a test for using private methods in static field initializers

Signed-off-by: Kubilay Kahveci <[email protected]>

* Add breaking checker test

Private methods inside class expressions should not error.

Signed-off-by: Kubilay Kahveci <[email protected]>

* Add to instances once per-instance

Signed-off-by: Kubilay Kahveci <[email protected]>

* Accept baseline

Signed-off-by: Kubilay Kahveci <[email protected]>

* fix: evaluate receiver and rhs expressions before throwing on readonly assignment

Signed-off-by: Kubilay Kahveci <[email protected]>

* Add a test for evaluating rhs before readonly assignment

Signed-off-by: Kubilay Kahveci <[email protected]>

* Transpile private accessors

Signed-off-by: Kubilay Kahveci <[email protected]>

* Accept baseline

Signed-off-by: Kubilay Kahveci <[email protected]>

* fix: handle readonly/writeonly accessors

Signed-off-by: Kubilay Kahveci <[email protected]>

* accept baseline

Signed-off-by: Kubilay Kahveci <[email protected]>

* add a test for private setter without a getter

Signed-off-by: Kubilay Kahveci <[email protected]>

* fix: getAllUnscopedEmitHelpers

Signed-off-by: Kubilay Kahveci <[email protected]>

* fix: better handling of duplicate names

Signed-off-by: Kubilay Kahveci <[email protected]>

* Fixed wrong error message for private methods in class expressions.

* change error message

Signed-off-by: Kubilay Kahveci <[email protected]>

* add a test for async private methods with a higher target

Signed-off-by: Kubilay Kahveci <[email protected]>

* fix: setter assignment returns rhs value

Signed-off-by: Kubilay Kahveci <[email protected]>

* add a test for setter assignment return value

Signed-off-by: Kubilay Kahveci <[email protected]>

* fix: handle duplicate accessors

Signed-off-by: Kubilay Kahveci <[email protected]>

* add tests for duplicate accessors

Signed-off-by: Kubilay Kahveci <[email protected]>

* docs: add missing parameter docs

Signed-off-by: Kubilay Kahveci <[email protected]>

* Fixed failing test.

* baseline-accept: ordering changes

Signed-off-by: Kubilay Kahveci <[email protected]>

* fix: attach weakSetName to property declaration

Signed-off-by: Kubilay Kahveci <[email protected]>

* add a test for nested private methods

Signed-off-by: Kubilay Kahveci <[email protected]>

* add a test with any

Signed-off-by: Kubilay Kahveci <[email protected]>

* Added support for static private fields accessors and methods.

* Added error message for private identifiers used with static decorators. There is no spec to go with this behavior as of yet.

* Fixed emit static bug that used private names outside of classes for initialization in esnext. Fixed issue where nested privates produce incorrect brand check.

* Added tests for private static fields methods and accessors.

* Fixed error messages and tests after merge.

* Accept new baseline.

* Improved duplicate identifier checks for static private class elements.

* Added error when using initializers with private static fields when useDefineForClassFields is not specified and target is esnext.

* Fixed code review issues.

* Removed semantically wrong emit on `useDefineForClassFields:true` with `target:esnext`

* Changed emit for uninitialized private static fields.

* Added runtime error in helper if a static private field is accessed before it was declared.

* Fixed code review comments for private identifier static class elements.

* add debug.assertNever for unknown node type (#53)

* Fixed code review issues.

* Fixed code review issues for private class elements.

* Fixes class shadowing when checking access to a private static class element.

* fix private methods/accessors in class expr inside a loop

* collapse switch case

* fix class name

* simplify getPrivateMethodsAndAccessors

* remove findPreviousAccessorInfo

* lazily create weakSetName identifier

* do not allocate a node if not needed in visitMehodDeclaration (#55)

* Removed all the emit helpers for private identifier methods accessors and modified the existing helpers for get and set fields to do the same job.

* Simplified emit for private identifier class elements.

* do not clone the receiver (#57)

* leave bad code in for #constructor and duplicate private names (#58)

* Added check for WeakSet collision.

* Added error for using a set only accessor.

* update keyof tests and ?? (#62)

* replace ?? with ||

* update keyof tests

* fix emit helpers comments

* produce an error if private field helpers are not up to date

* add tests

* fix setter-only compound assignment

* fix tests

* fix duplicated trailing comments (#64)

* clear receiver pos and setTextRange on helper calls

Co-authored-by: Kubilay Kahveci <[email protected]>
  • Loading branch information
dragomirtitian and mkubilayk authored Mar 25, 2021
1 parent 6ce82ab commit e638af7
Show file tree
Hide file tree
Showing 370 changed files with 15,654 additions and 1,223 deletions.
219 changes: 143 additions & 76 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

32 changes: 24 additions & 8 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -3288,6 +3288,26 @@
"category": "Error",
"code": 2802
},
"Cannot assign to private method '{0}'. Private methods are not writable.": {
"category": "Error",
"code": 2803
},
"Duplicate identifier '{0}'. Static and instance elements cannot share the same private name.": {
"category": "Error",
"code": 2804
},
"Static fields with private names can't have initializers when the '--useDefineForClassFields' flag is not specified with a '--target' of 'esnext'. Consider adding the '--useDefineForClassFields' flag.": {
"category": "Error",
"code": 2805
},
"Private accessor was defined without a getter.": {
"category": "Error",
"code": 2806
},
"This syntax requires an imported helper named '{1}' with {2} parameters, which is not compatible with the one in '{0}'. Consider upgrading your version of '{0}'.": {
"category": "Error",
"code": 2807
},

"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
Expand Down Expand Up @@ -6338,14 +6358,6 @@
"category": "Error",
"code": 18019
},
"A method cannot be named with a private identifier.": {
"category": "Error",
"code": 18022
},
"An accessor cannot be named with a private identifier.": {
"category": "Error",
"code": 18023
},
"An enum member cannot be named with a private identifier.": {
"category": "Error",
"code": 18024
Expand Down Expand Up @@ -6389,5 +6401,9 @@
"Invalid value for 'jsxFragmentFactory'. '{0}' is not a valid identifier or qualified-name.": {
"category": "Error",
"code": 18035
},
"Class decorators can't be used with static private identifier. Consider removing the experimental decorator.": {
"category": "Error",
"code": 18036
}
}
150 changes: 130 additions & 20 deletions src/compiler/factory/emitHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ namespace ts {
createImportDefaultHelper(expression: Expression): Expression;
createExportStarHelper(moduleExpression: Expression, exportsExpression?: Expression): Expression;
// Class Fields Helpers
createClassPrivateFieldGetHelper(receiver: Expression, privateField: Identifier): Expression;
createClassPrivateFieldSetHelper(receiver: Expression, privateField: Identifier, value: Expression): Expression;
createClassPrivateFieldGetHelper(receiver: Expression, state: Identifier, kind: PrivateIdentifierKind, f: Identifier | undefined): Expression;
createClassPrivateFieldSetHelper(receiver: Expression, state: Identifier, value: Expression, kind: PrivateIdentifierKind, f: Identifier | undefined): Expression;
}

export function createEmitHelperFactory(context: TransformationContext): EmitHelperFactory {
Expand Down Expand Up @@ -368,15 +368,30 @@ namespace ts {

// Class Fields Helpers

function createClassPrivateFieldGetHelper(receiver: Expression, privateField: Identifier) {
function createClassPrivateFieldGetHelper(receiver: Expression, state: Identifier, kind: PrivateIdentifierKind, f: Identifier | undefined) {
context.requestEmitHelper(classPrivateFieldGetHelper);
return factory.createCallExpression(getUnscopedHelperName("__classPrivateFieldGet"), /*typeArguments*/ undefined, [receiver, privateField]);
let args;
if (!f) {
args = [receiver, state, factory.createStringLiteral(kind)];
}
else {
args = [receiver, state, factory.createStringLiteral(kind), f];
}
return factory.createCallExpression(getUnscopedHelperName("__classPrivateFieldGet"), /*typeArguments*/ undefined, args);
}

function createClassPrivateFieldSetHelper(receiver: Expression, privateField: Identifier, value: Expression) {
function createClassPrivateFieldSetHelper(receiver: Expression, state: Identifier, value: Expression, kind: PrivateIdentifierKind, f: Identifier | undefined) {
context.requestEmitHelper(classPrivateFieldSetHelper);
return factory.createCallExpression(getUnscopedHelperName("__classPrivateFieldSet"), /*typeArguments*/ undefined, [receiver, privateField, value]);
let args;
if (!f) {
args = [receiver, state, value, factory.createStringLiteral(kind)];
}
else {
args = [receiver, state, value, factory.createStringLiteral(kind), f];
}
return factory.createCallExpression(getUnscopedHelperName("__classPrivateFieldSet"), /*typeArguments*/ undefined, args);
}

}

/* @internal */
Expand Down Expand Up @@ -803,7 +818,6 @@ namespace ts {
};`
};

// emit output for the __export helper function
export const exportStarHelper: UnscopedEmitHelper = {
name: "typescript:export-star",
importName: "__exportStar",
Expand All @@ -816,31 +830,127 @@ namespace ts {
};`
};

// Class fields helpers
/**
* Parameters:
* @param receiver — The object from which the private member will be read.
* @param state — One of the following:
* - A WeakMap used to read a private instance field.
* - A WeakSet used as an instance brand for private instance methods and accessors.
* - A function value that should be the undecorated class constructor used to brand check private static fields, methods, and accessors.
* @param kind — (optional pre TS 4.3, required for TS 4.3+) One of the following values:
* - undefined — Indicates a private instance field (pre TS 4.3).
* - "f" — Indicates a private field (instance or static).
* - "m" — Indicates a private method (instance or static).
* - "a" — Indicates a private accessor (instance or static).
* @param f — (optional pre TS 4.3) Depends on the arguments for state and kind:
* - If kind is "m", this should be the function corresponding to the static or instance method.
* - If kind is "a", this should be the function corresponding to the getter method, or undefined if the getter was not defined.
* - If kind is "f" and state is a function, this should be an object holding the value of a static field, or undefined if the static field declaration has not yet been evaluated.
* Usage:
* This helper will only ever be used by the compiler in the following ways:
*
* Reading from a private instance field (pre TS 4.3):
* __classPrivateFieldGet(<any>, <WeakMap>)
*
* Reading from a private instance field (TS 4.3+):
* __classPrivateFieldGet(<any>, <WeakMap>, "f")
*
* Reading from a private instance get accessor (when defined, TS 4.3+):
* __classPrivateFieldGet(<any>, <WeakSet>, "a", <function>)
*
* Reading from a private instance get accessor (when not defined, TS 4.3+):
* __classPrivateFieldGet(<any>, <WeakSet>, "a", void 0)
* NOTE: This always results in a runtime error.
*
* Reading from a private instance method (TS 4.3+):
* __classPrivateFieldGet(<any>, <WeakSet>, "m", <function>)
*
* Reading from a private static field (TS 4.3+):
* __classPrivateFieldGet(<any>, <constructor>, "f", <{ value: any }>)
*
* Reading from a private static get accessor (when defined, TS 4.3+):
* __classPrivateFieldGet(<any>, <constructor>, "a", <function>)
*
* Reading from a private static get accessor (when not defined, TS 4.3+):
* __classPrivateFieldGet(<any>, <constructor>, "a", void 0)
* NOTE: This always results in a runtime error.
*
* Reading from a private static method (TS 4.3+):
* __classPrivateFieldGet(<any>, <constructor>, "m", <function>)
*/
export const classPrivateFieldGetHelper: UnscopedEmitHelper = {
name: "typescript:classPrivateFieldGet",
importName: "__classPrivateFieldGet",
scoped: false,
text: `
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, privateMap) {
if (!privateMap.has(receiver)) {
throw new TypeError("attempted to get private field on non-instance");
}
return privateMap.get(receiver);
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};`
};

/**
* Parameters:
* @param receiver — The object on which the private member will be set.
* @param state — One of the following:
* - A WeakMap used to store a private instance field.
* - A WeakSet used as an instance brand for private instance methods and accessors.
* - A function value that should be the undecorated class constructor used to brand check private static fields, methods, and accessors.
* @param value — The value to set.
* @param kind — (optional pre TS 4.3, required for TS 4.3+) One of the following values:
* - undefined — Indicates a private instance field (pre TS 4.3).
* - "f" — Indicates a private field (instance or static).
* - "m" — Indicates a private method (instance or static).
* - "a" — Indicates a private accessor (instance or static).
* @param f — (optional pre TS 4.3) Depends on the arguments for state and kind:
* - If kind is "m", this should be the function corresponding to the static or instance method.
* - If kind is "a", this should be the function corresponding to the setter method, or undefined if the setter was not defined.
* - If kind is "f" and state is a function, this should be an object holding the value of a static field, or undefined if the static field declaration has not yet been evaluated.
* Usage:
* This helper will only ever be used by the compiler in the following ways:
*
* Writing to a private instance field (pre TS 4.3):
* __classPrivateFieldSet(<any>, <WeakMap>, <any>)
*
* Writing to a private instance field (TS 4.3+):
* __classPrivateFieldSet(<any>, <WeakMap>, <any>, "f")
*
* Writing to a private instance set accessor (when defined, TS 4.3+):
* __classPrivateFieldSet(<any>, <WeakSet>, <any>, "a", <function>)
*
* Writing to a private instance set accessor (when not defined, TS 4.3+):
* __classPrivateFieldSet(<any>, <WeakSet>, <any>, "a", void 0)
* NOTE: This always results in a runtime error.
*
* Writing to a private instance method (TS 4.3+):
* __classPrivateFieldSet(<any>, <WeakSet>, <any>, "m", <function>)
* NOTE: This always results in a runtime error.
*
* Writing to a private static field (TS 4.3+):
* __classPrivateFieldSet(<any>, <constructor>, <any>, "f", <{ value: any }>)
*
* Writing to a private static set accessor (when defined, TS 4.3+):
* __classPrivateFieldSet(<any>, <constructor>, <any>, "a", <function>)
*
* Writing to a private static set accessor (when not defined, TS 4.3+):
* __classPrivateFieldSet(<any>, <constructor>, <any>, "a", void 0)
* NOTE: This always results in a runtime error.
*
* Writing to a private static method (TS 4.3+):
* __classPrivateFieldSet(<any>, <constructor>, <any>, "m", <function>)
* NOTE: This always results in a runtime error.
*/
export const classPrivateFieldSetHelper: UnscopedEmitHelper = {
name: "typescript:classPrivateFieldSet",
importName: "__classPrivateFieldSet",
scoped: false,
text: `
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, privateMap, value) {
if (!privateMap.has(receiver)) {
throw new TypeError("attempted to set private field on non-instance");
}
privateMap.set(receiver, value);
return value;
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};`
};

Expand Down Expand Up @@ -897,4 +1007,4 @@ namespace ts {
&& (getEmitFlags(firstSegment.expression) & EmitFlags.HelperName)
&& firstSegment.expression.escapedText === helperName;
}
}
}
Loading

0 comments on commit e638af7

Please sign in to comment.