- Notifications
You must be signed in to change notification settings - Fork 13k
Implement Static Private Class Elements #42986
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement Static Private Class Elements #42986
Conversation
…ing except classes. Changes objects literal checking to not bail on first private name found in object literal.
Signed-off-by: Kubilay Kahveci <kahvecikubilay@gmail.com>
Signed-off-by: Kubilay Kahveci <kahvecikubilay@gmail.com>
Signed-off-by: Kubilay Kahveci <kahvecikubilay@gmail.com>
Signed-off-by: Kubilay Kahveci <kahvecikubilay@gmail.com>
Signed-off-by: Kubilay Kahveci <kahvecikubilay@gmail.com>
Signed-off-by: Kubilay Kahveci <kahvecikubilay@gmail.com>
Private methods inside class expressions should not error. Signed-off-by: Kubilay Kahveci <kahvecikubilay@gmail.com>
Signed-off-by: Kubilay Kahveci <kahvecikubilay@gmail.com>
Signed-off-by: Kubilay Kahveci <kahvecikubilay@gmail.com>
…y assignment Signed-off-by: Kubilay Kahveci <kahvecikubilay@gmail.com>
Signed-off-by: Kubilay Kahveci <kahvecikubilay@gmail.com>
Signed-off-by: Kubilay Kahveci <kahvecikubilay@gmail.com>
Signed-off-by: Kubilay Kahveci <kahvecikubilay@gmail.com>
Signed-off-by: Kubilay Kahveci <kahvecikubilay@gmail.com>
Signed-off-by: Kubilay Kahveci <kahvecikubilay@gmail.com>
Signed-off-by: Kubilay Kahveci <kahvecikubilay@gmail.com>
Signed-off-by: Kubilay Kahveci <kahvecikubilay@gmail.com>
Signed-off-by: Kubilay Kahveci <kahvecikubilay@gmail.com>
Signed-off-by: Kubilay Kahveci <kahvecikubilay@gmail.com>
Signed-off-by: Kubilay Kahveci <kahvecikubilay@gmail.com>
Signed-off-by: Kubilay Kahveci <kahvecikubilay@gmail.com>
Signed-off-by: Kubilay Kahveci <kahvecikubilay@gmail.com>
Signed-off-by: Kubilay Kahveci <kahvecikubilay@gmail.com>
Signed-off-by: Kubilay Kahveci <kahvecikubilay@gmail.com>
Signed-off-by: Kubilay Kahveci <kahvecikubilay@gmail.com>
"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.": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh we need to workshop this one a bit more.
class A_Field_Field { | ||
#foo = "foo"; | ||
#foo = "foo"; | ||
~~~~ | ||
!!! error TS2300: Duplicate identifier '#foo'. | ||
} | ||
| ||
// Error | ||
class A_Field_Method { | ||
#foo = "foo"; | ||
~~~~ | ||
!!! error TS2300: Duplicate identifier '#foo'. | ||
#foo() { } | ||
~~~~ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's surprising that there's any inconsistency here. Any idea why that's happening?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am unsure of the exact reasons. Some duplication errors come from the binder some from the checker. I think this has something to do with overloads. The effect is that if the duplication is field-method the error comes from the binder (it also has info about the other class element). While duplication for method-field comes from the checker (and does not have related info about the other class element).
This is the reason I massively expanded the duplication tests, as since the errors can come from two different places and order matters, I wanted to make sure I don't miss any corner cases.
I think the duplicate detection code might be due for a revamp. It was quite confusing to have the error to come from different places if the order differs. I tried to just make the changes needed for private static-instance duplication duplication, as such a revamp was not really in scope for this issue.
return !isOptionalChain(node) && !!findAncestor(node, parent => parent === declClass); | ||
} | ||
return checkPropertyAccessibility(node, isSuper, type, prop); | ||
return checkPropertyAccessibility(node, isSuper, type, prop, /* reportError */ false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So what exactly are we trying to suppress here? It's unclear why this is needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a check in getSuggestedSymbolForNonexistentProperty
so that it only suggests accessible members. To do this I use isValidPropertyAccessForCompletions
which in turn uses checkPropertyAccessibility
.
props = filter(props, prop => isValidPropertyAccessForCompletions(parent, containingType, prop));
The problem is that since checkPropertyAccessibility
reports errors, when the compiler tries to get the suggestion, it will report access errors on all tested properties in that filter
call. Suppressing the error seems to cause no other issues. isValidPropertyAccessForCompletions
isn't named with a check
prefix like other methods that report errors, so it is a bit strange it would report errors.
I could remove this improvement and allow invalid suggestions but I think this is a net improvement.
This PR is dependent on the PR for private methods and accessors. So some of the commits here are also found in that PR (#42458) Since the PR for instance methods and accessors is not yet merged this causes some issues when reviewing this PR. Some of the issues in the code review are in the code for private methods and accessors, while others come from static class elements. Would you prefer that I merge them so that we only have one PR? (retaining the features as separate commits) Initially we thought it might be easier to keep them separate for ease of review, but we can change the description of this PR and have a single unified PR. |
@dragomirtitian You might want to mark this PR as a draft until #42458 is merged. |
…static-fields-methods-and-accessors
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still have more to review (I've already listed some feedback on #42458), but again I think we could condense these 14 helpers down into the existing two:
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, privateState, value, kind, f) { if (typeof privateState === "function" ? receiver !== privateState : !privateState.has(receiver)) { throw new TypeError("Private state access of wrong provenance"); } if (kind === "method") throw new TypeError("Attempted to set private method"); if (kind === "accessor") { if (!f) throw new TypeError("Private element is not writable"); f.call(receiver, value); } else if (typeof privateState === "function") { if (!f) throw new TypeError("Private static field was accessed before its declaration"); f.value = value; } else { privateState.set(receiver, value); } return value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, privateState, kind, f) { if (typeof privateState === "function" ? receiver !== privateState : !privateState.has(receiver)) { throw new TypeError("Private state access of wrong provenance"); } if (kind === "method") return f; if (kind === "accessor") { if (!f) throw new TypeError("Private element is not readable"); return f.call(receiver); } if (typeof privateState === "function") { if (!f) throw new TypeError("Private static field was accessed before its declaration"); return f.value; } return privateState.get(receiver); };
I'd like to get @DanielRosenwasser's opinion on the helper count vs helper size though.
createClassPrivateMethodGetHelper(receiver: Expression, instances: Identifier, fn: Identifier): Expression; | ||
createClassPrivateReadonlyHelper(receiver: Expression, value: Expression): Expression; | ||
createClassPrivateWriteonlyHelper(receiver: Expression): Expression; | ||
createClassPrivateAccessorGetHelper(receiver: Expression, instances: Identifier, fn: Identifier): Expression; | ||
createClassPrivateAccessorSetHelper(receiver: Expression, instances: Identifier, fn: Identifier, value: Expression): Expression; | ||
// Class Static Private Helpers | ||
createClassStaticPrivateFieldGetHelper(receiver: Expression, classConstructor: Identifier, privateField: Identifier): Expression; | ||
createClassStaticPrivateFieldSetHelper(receiver: Expression, classConstructor: Identifier, privateField: Identifier, value: Expression): Expression; | ||
createClassStaticPrivateMethodGetHelper(receiver: Expression, classConstructor: Identifier, fn: Identifier): Expression; | ||
createClassStaticPrivateReadonlyHelper(receiver: Expression, value: Expression): Expression; | ||
createClassStaticPrivateWriteonlyHelper(receiver: Expression): Expression; | ||
createClassStaticPrivateAccessorGetHelper(receiver: Expression, classConstructor: Identifier, fn: Identifier): Expression; | ||
createClassStaticPrivateAccessorSetHelper(receiver: Expression, classConstructor: Identifier, fn: Identifier, value: Expression): Expression; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This adds a large number of emit helpers that are extremely similar. Perhaps we could condense them down? In #42458 I suggested extending the existing __classPrivateFieldGet
and __classPrivateFieldSet
helpers in a backwards-compatible fashion. While that suggestion does make those helper functions larger than they are currently, if you were to write a file that exercised every single one of these helpers you could end up with significantly larger emit.
If we're willing to give up the expressiveness of the runtime assertions in cases where we already report compile-time errors, then the two-helper emit can be even further reduced: var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (typeof state === "function" ? receiver !== state : !state.has(receiver)) throw new TypeError("Private state access of wrong provenance"); if (kind === "method") throw new TypeError("Attempted to set private method"); if (kind === "accessor") f.call(receiver, value); else if (typeof state === "function") f.value = value; else state.set(receiver, value); return value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (typeof state === "function" ? receiver !== state : !state.has(receiver)) throw new TypeError("Private state access of wrong provenance"); return kind === "method" ? f : kind === "accessor" ? f.call(receiver) : typeof state === "function" ? f.value : state.get(receiver); }; The above still errors at runtime in the same places (since var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (typeof state === "function" ? receiver !== state : !state.has(receiver)) throw new TypeError("Private state access of wrong provenance"); if (kind === "method") throw new TypeError("Attempted to set private method"); return kind === "accessor" ? (f.call(receiver, value), value) : typeof state === "function" ? f.value = value : (state.set(receiver, value), value); }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (typeof state === "function" ? receiver !== state : !state.has(receiver)) throw new TypeError("Private state access of wrong provenance"); return kind === "method" ? f : kind === "accessor" ? f.call(receiver) : typeof state === "function" ? f.value : state.get(receiver); }; Which is 9 lines of code as opposed to 14 separate helpers. |
I've further reduced the overall size of my proposal for a unified var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "method") throw new TypeError("Private method is not writable"); if (kind === "accessor" && !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 === "accessor" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "accessor" && !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 === "method" ? f : kind === "accessor" ? f.call(receiver) : f ? f.value : state.get(receiver); }; I've written up a full breakdown of these helpers here: https://gist.github.com/rbuckton/02e3e9f13e4405184a1e4cfe2da69634 |
Closing the PR in favor of #42458 (which now contains the static private class members) |
Static Private Class Elements
This PR implements the TC39 Stage 3 Static Class features which introduce private identifier static class elements (fields/methods/accessors).
Fixes: #42985
Dependencies
This PR depends on the existing PR for Private Methods & Accessors #42458
Open issues
tslib
Downlevel
JavaScript - ES2020 emit
Helper functions
References
Wider Contributions
The following Babel issues were discovered and reported during this work:
Credits
This PR includes contributions from the following Bloomberg engineers:
Design Limitations
Example
Initializers are not allowed if
target: esnext
ANDuseDefineForClassFields: false
. This is due to the fact that initializing private fields outside of a class is a non-trivial transform (a possible solution is described here - a modified version of it could be applied if there is desire for this combination to allow it) and keeping the init in the class would change runtime ordering of initializers.this
is not allow in static initialization expressions. The restriction is a pre-existing issue and so is considered out-of-scope of this PR.Unlike instance #private class elements, static #private class elements do not depend on
WeakMap
orWeakSet
. This means that technically we could transpile static #private class elements fores5
or evenes3
. However having different requirements for instance vs static #private class elements would probably cause more confusion than benefit. Therefore we retain the existing minimum target ofes2015
for using static #private class elements and will error otherwise.