Skip to content

Commit e638af7

Browse files
ES private class elements (#42458)
* 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]>
1 parent 6ce82ab commit e638af7

File tree

370 files changed

+15654
-1223
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

370 files changed

+15654
-1223
lines changed

src/compiler/checker.ts

+143-76
Large diffs are not rendered by default.

src/compiler/diagnosticMessages.json

+24-8
Original file line numberDiff line numberDiff line change
@@ -3288,6 +3288,26 @@
32883288
"category": "Error",
32893289
"code": 2802
32903290
},
3291+
"Cannot assign to private method '{0}'. Private methods are not writable.": {
3292+
"category": "Error",
3293+
"code": 2803
3294+
},
3295+
"Duplicate identifier '{0}'. Static and instance elements cannot share the same private name.": {
3296+
"category": "Error",
3297+
"code": 2804
3298+
},
3299+
"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.": {
3300+
"category": "Error",
3301+
"code": 2805
3302+
},
3303+
"Private accessor was defined without a getter.": {
3304+
"category": "Error",
3305+
"code": 2806
3306+
},
3307+
"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}'.": {
3308+
"category": "Error",
3309+
"code": 2807
3310+
},
32913311

32923312
"Import declaration '{0}' is using private name '{1}'.": {
32933313
"category": "Error",
@@ -6338,14 +6358,6 @@
63386358
"category": "Error",
63396359
"code": 18019
63406360
},
6341-
"A method cannot be named with a private identifier.": {
6342-
"category": "Error",
6343-
"code": 18022
6344-
},
6345-
"An accessor cannot be named with a private identifier.": {
6346-
"category": "Error",
6347-
"code": 18023
6348-
},
63496361
"An enum member cannot be named with a private identifier.": {
63506362
"category": "Error",
63516363
"code": 18024
@@ -6389,5 +6401,9 @@
63896401
"Invalid value for 'jsxFragmentFactory'. '{0}' is not a valid identifier or qualified-name.": {
63906402
"category": "Error",
63916403
"code": 18035
6404+
},
6405+
"Class decorators can't be used with static private identifier. Consider removing the experimental decorator.": {
6406+
"category": "Error",
6407+
"code": 18036
63926408
}
63936409
}

src/compiler/factory/emitHelpers.ts

+130-20
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ namespace ts {
3232
createImportDefaultHelper(expression: Expression): Expression;
3333
createExportStarHelper(moduleExpression: Expression, exportsExpression?: Expression): Expression;
3434
// Class Fields Helpers
35-
createClassPrivateFieldGetHelper(receiver: Expression, privateField: Identifier): Expression;
36-
createClassPrivateFieldSetHelper(receiver: Expression, privateField: Identifier, value: Expression): Expression;
35+
createClassPrivateFieldGetHelper(receiver: Expression, state: Identifier, kind: PrivateIdentifierKind, f: Identifier | undefined): Expression;
36+
createClassPrivateFieldSetHelper(receiver: Expression, state: Identifier, value: Expression, kind: PrivateIdentifierKind, f: Identifier | undefined): Expression;
3737
}
3838

3939
export function createEmitHelperFactory(context: TransformationContext): EmitHelperFactory {
@@ -368,15 +368,30 @@ namespace ts {
368368

369369
// Class Fields Helpers
370370

371-
function createClassPrivateFieldGetHelper(receiver: Expression, privateField: Identifier) {
371+
function createClassPrivateFieldGetHelper(receiver: Expression, state: Identifier, kind: PrivateIdentifierKind, f: Identifier | undefined) {
372372
context.requestEmitHelper(classPrivateFieldGetHelper);
373-
return factory.createCallExpression(getUnscopedHelperName("__classPrivateFieldGet"), /*typeArguments*/ undefined, [receiver, privateField]);
373+
let args;
374+
if (!f) {
375+
args = [receiver, state, factory.createStringLiteral(kind)];
376+
}
377+
else {
378+
args = [receiver, state, factory.createStringLiteral(kind), f];
379+
}
380+
return factory.createCallExpression(getUnscopedHelperName("__classPrivateFieldGet"), /*typeArguments*/ undefined, args);
374381
}
375382

376-
function createClassPrivateFieldSetHelper(receiver: Expression, privateField: Identifier, value: Expression) {
383+
function createClassPrivateFieldSetHelper(receiver: Expression, state: Identifier, value: Expression, kind: PrivateIdentifierKind, f: Identifier | undefined) {
377384
context.requestEmitHelper(classPrivateFieldSetHelper);
378-
return factory.createCallExpression(getUnscopedHelperName("__classPrivateFieldSet"), /*typeArguments*/ undefined, [receiver, privateField, value]);
385+
let args;
386+
if (!f) {
387+
args = [receiver, state, value, factory.createStringLiteral(kind)];
388+
}
389+
else {
390+
args = [receiver, state, value, factory.createStringLiteral(kind), f];
391+
}
392+
return factory.createCallExpression(getUnscopedHelperName("__classPrivateFieldSet"), /*typeArguments*/ undefined, args);
379393
}
394+
380395
}
381396

382397
/* @internal */
@@ -803,7 +818,6 @@ namespace ts {
803818
};`
804819
};
805820

806-
// emit output for the __export helper function
807821
export const exportStarHelper: UnscopedEmitHelper = {
808822
name: "typescript:export-star",
809823
importName: "__exportStar",
@@ -816,31 +830,127 @@ namespace ts {
816830
};`
817831
};
818832

819-
// Class fields helpers
833+
/**
834+
* Parameters:
835+
* @param receiver — The object from which the private member will be read.
836+
* @param state — One of the following:
837+
* - A WeakMap used to read a private instance field.
838+
* - A WeakSet used as an instance brand for private instance methods and accessors.
839+
* - A function value that should be the undecorated class constructor used to brand check private static fields, methods, and accessors.
840+
* @param kind — (optional pre TS 4.3, required for TS 4.3+) One of the following values:
841+
* - undefined — Indicates a private instance field (pre TS 4.3).
842+
* - "f" — Indicates a private field (instance or static).
843+
* - "m" — Indicates a private method (instance or static).
844+
* - "a" — Indicates a private accessor (instance or static).
845+
* @param f — (optional pre TS 4.3) Depends on the arguments for state and kind:
846+
* - If kind is "m", this should be the function corresponding to the static or instance method.
847+
* - If kind is "a", this should be the function corresponding to the getter method, or undefined if the getter was not defined.
848+
* - 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.
849+
* Usage:
850+
* This helper will only ever be used by the compiler in the following ways:
851+
*
852+
* Reading from a private instance field (pre TS 4.3):
853+
* __classPrivateFieldGet(<any>, <WeakMap>)
854+
*
855+
* Reading from a private instance field (TS 4.3+):
856+
* __classPrivateFieldGet(<any>, <WeakMap>, "f")
857+
*
858+
* Reading from a private instance get accessor (when defined, TS 4.3+):
859+
* __classPrivateFieldGet(<any>, <WeakSet>, "a", <function>)
860+
*
861+
* Reading from a private instance get accessor (when not defined, TS 4.3+):
862+
* __classPrivateFieldGet(<any>, <WeakSet>, "a", void 0)
863+
* NOTE: This always results in a runtime error.
864+
*
865+
* Reading from a private instance method (TS 4.3+):
866+
* __classPrivateFieldGet(<any>, <WeakSet>, "m", <function>)
867+
*
868+
* Reading from a private static field (TS 4.3+):
869+
* __classPrivateFieldGet(<any>, <constructor>, "f", <{ value: any }>)
870+
*
871+
* Reading from a private static get accessor (when defined, TS 4.3+):
872+
* __classPrivateFieldGet(<any>, <constructor>, "a", <function>)
873+
*
874+
* Reading from a private static get accessor (when not defined, TS 4.3+):
875+
* __classPrivateFieldGet(<any>, <constructor>, "a", void 0)
876+
* NOTE: This always results in a runtime error.
877+
*
878+
* Reading from a private static method (TS 4.3+):
879+
* __classPrivateFieldGet(<any>, <constructor>, "m", <function>)
880+
*/
820881
export const classPrivateFieldGetHelper: UnscopedEmitHelper = {
821882
name: "typescript:classPrivateFieldGet",
822883
importName: "__classPrivateFieldGet",
823884
scoped: false,
824885
text: `
825-
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, privateMap) {
826-
if (!privateMap.has(receiver)) {
827-
throw new TypeError("attempted to get private field on non-instance");
828-
}
829-
return privateMap.get(receiver);
886+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
887+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
888+
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");
889+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
830890
};`
831891
};
832892

893+
/**
894+
* Parameters:
895+
* @param receiver — The object on which the private member will be set.
896+
* @param state — One of the following:
897+
* - A WeakMap used to store a private instance field.
898+
* - A WeakSet used as an instance brand for private instance methods and accessors.
899+
* - A function value that should be the undecorated class constructor used to brand check private static fields, methods, and accessors.
900+
* @param value — The value to set.
901+
* @param kind — (optional pre TS 4.3, required for TS 4.3+) One of the following values:
902+
* - undefined — Indicates a private instance field (pre TS 4.3).
903+
* - "f" — Indicates a private field (instance or static).
904+
* - "m" — Indicates a private method (instance or static).
905+
* - "a" — Indicates a private accessor (instance or static).
906+
* @param f — (optional pre TS 4.3) Depends on the arguments for state and kind:
907+
* - If kind is "m", this should be the function corresponding to the static or instance method.
908+
* - If kind is "a", this should be the function corresponding to the setter method, or undefined if the setter was not defined.
909+
* - 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.
910+
* Usage:
911+
* This helper will only ever be used by the compiler in the following ways:
912+
*
913+
* Writing to a private instance field (pre TS 4.3):
914+
* __classPrivateFieldSet(<any>, <WeakMap>, <any>)
915+
*
916+
* Writing to a private instance field (TS 4.3+):
917+
* __classPrivateFieldSet(<any>, <WeakMap>, <any>, "f")
918+
*
919+
* Writing to a private instance set accessor (when defined, TS 4.3+):
920+
* __classPrivateFieldSet(<any>, <WeakSet>, <any>, "a", <function>)
921+
*
922+
* Writing to a private instance set accessor (when not defined, TS 4.3+):
923+
* __classPrivateFieldSet(<any>, <WeakSet>, <any>, "a", void 0)
924+
* NOTE: This always results in a runtime error.
925+
*
926+
* Writing to a private instance method (TS 4.3+):
927+
* __classPrivateFieldSet(<any>, <WeakSet>, <any>, "m", <function>)
928+
* NOTE: This always results in a runtime error.
929+
*
930+
* Writing to a private static field (TS 4.3+):
931+
* __classPrivateFieldSet(<any>, <constructor>, <any>, "f", <{ value: any }>)
932+
*
933+
* Writing to a private static set accessor (when defined, TS 4.3+):
934+
* __classPrivateFieldSet(<any>, <constructor>, <any>, "a", <function>)
935+
*
936+
* Writing to a private static set accessor (when not defined, TS 4.3+):
937+
* __classPrivateFieldSet(<any>, <constructor>, <any>, "a", void 0)
938+
* NOTE: This always results in a runtime error.
939+
*
940+
* Writing to a private static method (TS 4.3+):
941+
* __classPrivateFieldSet(<any>, <constructor>, <any>, "m", <function>)
942+
* NOTE: This always results in a runtime error.
943+
*/
833944
export const classPrivateFieldSetHelper: UnscopedEmitHelper = {
834945
name: "typescript:classPrivateFieldSet",
835946
importName: "__classPrivateFieldSet",
836947
scoped: false,
837948
text: `
838-
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, privateMap, value) {
839-
if (!privateMap.has(receiver)) {
840-
throw new TypeError("attempted to set private field on non-instance");
841-
}
842-
privateMap.set(receiver, value);
843-
return value;
949+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
950+
if (kind === "m") throw new TypeError("Private method is not writable");
951+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
952+
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");
953+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
844954
};`
845955
};
846956

@@ -897,4 +1007,4 @@ namespace ts {
8971007
&& (getEmitFlags(firstSegment.expression) & EmitFlags.HelperName)
8981008
&& firstSegment.expression.escapedText === helperName;
8991009
}
900-
}
1010+
}

0 commit comments

Comments
 (0)