es private fields in in#52
Conversation
0cf6e7d to
30f4d03
Compare
e76ef1b to
d603860
Compare
| const id = parsePrivateIdentifier(); | ||
| if (token() !== SyntaxKind.InKeyword) { | ||
| // TODO(aclaymore) use better error | ||
| return createMissingNode(SyntaxKind.InKeyword, /*reportAtCurrentPosition*/ true, Diagnostics._0_expected, tokenToString(SyntaxKind.InKeyword)); |
There was a problem hiding this comment.
Maybe we should think of common parsing mistakes like if(#prop.a in foo). This currently issues the error 'in' expected.. I would argue an error along the lines of: You might have meant this.#props.a would be better,
There was a problem hiding this comment.
Looking into this there are existing errors and quickFixes for 'bare' privateFields but my branch was preventing them from running as I was consuming the field in the parser. I've changed the parser to lookahead and only match when the in keyword is present.
| if (#p1 in b) { | ||
| b; // good b is 'Bar & Foo' | ||
| } else { | ||
| b; // good b is Bar | ||
| } |
There was a problem hiding this comment.
I wonder if we shouldn't be more strict here. I mean for b instanceof Foo it makes sense since you have Symbol.species which can customize how instanceof works. But for #p1 in b there is not universe in which #p1 can be in b and it also be an instance of Bar (if Bar is a class). For other object types this still makes sense... so I'm rather torn if we should issue an error or not.
There was a problem hiding this comment.
Interesting. I'm thinking not unless the rule is applied more generally, as in all productions of ClassA & ClassB would be never if at least one class had private fields, and the other was not a sub/superType.
Surprisingly there are cases where the runtime type here could be Bar and not Foo, though I wouldn't expect this to be common code:
class SuperReturn {
constructor(v) { return v; }
}
class C extends SuperReturn {
#priv;
static isC(v) { return #priv in v }
cMethod() {}
}
class Unrelated {
random = 1;
uMethod();
}
const u = new C(new Unrelated));
if (C.isC(u)) {
u.random; // 1
u.uMethod(); // fine
u.cMethod(); // throws
}
tests/baselines/reference/privateNameInInExpressionUnused.errors.txt
Outdated
Show resolved
Hide resolved
|
Another thing I've noticed is that |
Signed-off-by: Ashley Claymore <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
f0f169e to
42b35c1
Compare
Signed-off-by: Ashley Claymore <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
42b35c1 to
02f59a3
Compare
|
@dragomirtitian - I've rebased onto TypeScript post the private methods & static fields PR. And ready for another round of review when you have the time. |
src/compiler/checker.ts
Outdated
| } | ||
| const classSymbol = symbol.parent!; | ||
| const classType = <InterfaceType>getTypeOfSymbol(classSymbol); | ||
| const firstDecl = symbol.declarations?.[0]; |
There was a problem hiding this comment.
Wasn't there a valueDeclaration that is what is needed here ?
There was a problem hiding this comment.
ah yes there is symbol.valueDeclaration - that looks much better. I'll change to that.
src/compiler/checker.ts
Outdated
| const firstDecl = symbol.declarations?.[0]; | ||
| Debug.assert(firstDecl, "should always have a declaration"); | ||
| let targetType: Type; | ||
| if (hasSyntacticModifier(firstDecl, ModifierFlags.Static)) { |
src/compiler/checker.ts
Outdated
| const ctorSigs = getSignaturesOfType(classType, SignatureKind.Construct); | ||
| Debug.assert(ctorSigs.length > 0, "should always have a constructor"); | ||
| targetType = getReturnTypeOfSignature(ctorSigs[0]); |
There was a problem hiding this comment.
There seems to be a better way to get the instance type:
const classSymbol = getSymbolOfNode(classDecl);
const classInstanceType = <InterfaceType>getDeclaredTypeOfSymbol(classSymbol);At line (24161, fn: classDeclarationExtendsNull)
Not sure if 100% equivalent or if better, but please consider it.
| @@ -0,0 +1,143 @@ | |||
| tests/cases/conformance/classes/members/privateNames/privateNameInInExpression.ts(3,27): error TS2805: 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.
Nit: Consider removing this error from the test by changing compiler settings. It is noise here IMO
|
|
||
| const e = #field in (v as never); | ||
|
|
||
| for (let f in #field in v as any) { /**/ } // unlikely but valid |
There was a problem hiding this comment.
Took a second to get this was actually for (let f in (#field in v as any)) 😅.
There was a problem hiding this comment.
haha! I hope no one ever does this but at least we support it if they try
mkubilayk
left a comment
There was a problem hiding this comment.
Tried it out in VSCode, this is looking good! There is one case I found a bit inconvenient but I'm not sure if we can improve it. When you are inside PrivateIdentifierInInExpression and editing the identifier:
if (# in this) {
^ cursor
}available private identifiers are listed correctly in the suggestions (ctrl+space). But when you pick one if them, you get a property access (this.#foo). If we could use the contextual information to drive this and insert #foo instead, that would be great. If you think people might want this.#foo more often than #foo in this case, please ignore my suggestion.
src/compiler/utilities.ts
Outdated
| return getBinaryOperatorPrecedence(operatorKind); | ||
| } | ||
|
|
||
| case SyntaxKind.PrivateIdentifierInInExpression: |
There was a problem hiding this comment.
nit: This can be combined with SyntaxKind.AsExpression case.
src/compiler/visitorPublic.ts
Outdated
|
|
||
| case SyntaxKind.PrivateIdentifierInInExpression: | ||
| return factory.updatePrivateIdentifierInInExpression(<PrivateIdentifierInInExpression>node, | ||
| nodeVisitor((<PrivateIdentifierInInExpression>node).name, visitor, isExpression), |
There was a problem hiding this comment.
Should this use isPrivateIdentifier check?
| nodeVisitor((<PrivateIdentifierInInExpression>node).name, visitor, isExpression), | |
| nodeVisitor((<PrivateIdentifierInInExpression>node).name, visitor, isPrivateIdentifier), | |
There was a problem hiding this comment.
If my memory serves correctly: no. As I believe this check is also done on the transformed AST where PrivateIdentifierInInExpression becomes a methodCall.
This was over a month ago so I may be getting this mixed up with a different bit of code. I'll try it out again and confirm either way.
There was a problem hiding this comment.
Played around to jog my memory. The reason this was isExpression and not isPrivateIdentifier was because when the identifier is invalid (e.g. used outside of a class) the classFields transformer emits an empty plain Identifier.
I've changed this to isMemberName now to match both private and standard identifiers.
Yeah I was really unsure on this too. My assumption was that people are more likely to reach for accessing the private field but that's not based on any evidence just a gut feeling. Maybe it's possible to offer both suggestions a plain |
- simplify obtaining private fields's static or instance type - make visitor node test more specific - clean up warnings about static class fields in tests Signed-off-by: Ashley Claymore <[email protected]>
f0bbed9 to
8928039
Compare
|
I spent a little bit of time seeing if I could change the completions private fields to sometimes complete to just the identifier but couldn't get anything working quickly. I'll have another go next time I have some time. |
Signed-off-by: Ashley Claymore <[email protected]>
Signed-off-by: Ashley Claymore <[email protected]>
|
Merged latest TypeScript main into this branch. All tests still passing. |
mkubilayk
left a comment
There was a problem hiding this comment.
I think this looks good. Left one comment to prevent against a potential future bug.
We can use microsoft#42458 as an example for the PR description when we open the upstream PR.
| * Visits `#id in expr` | ||
| */ | ||
| function visitPrivateIdentifierInInExpression(node: PrivateIdentifierInInExpression) { | ||
| if (!shouldTransformPrivateElements) { |
There was a problem hiding this comment.
This is probably fine today but might be a problem when ES2022 is introduced. shouldTransformPrivateElements will probably be updated to say languageVersion < ScriptTarget.ES2022. But it's possible that proposal-private-fields-in-in won't make it to ES2022.
There was a problem hiding this comment.
The current plan is to assume https://github.com/tc39/proposal-private-fields-in-in will make stage 4 in time for ES2022. There are positive signals of this, as it is already available in latest Chrome and Firefox.
* es private fields in in (bloomberg#52) add support for the 'private-fields-in-in' TC39 proposal Signed-off-by: Ashley Claymore <[email protected]> * [fixup] include inToken when walking forEachChild(node, cb) * [squash] re-accept lib definition baseline changes * [squash] reduce if/else to ternary Signed-off-by: Ashley Claymore <[email protected]> * [squash] drop 'originalName' and rename parameter instead Signed-off-by: Ashley Claymore <[email protected]> * [squash] extend spelling suggestion to all privateIdentifiers Signed-off-by: Ashley Claymore <[email protected]> * [squash] revert the added lexical spelling suggestions logic Signed-off-by: Ashley Claymore <[email protected]> * [squash] update baseline Signed-off-by: Ashley Claymore <[email protected]> * [squash] inline variable as per PR suggestion Signed-off-by: Ashley Claymore <[email protected]> * [squash] test targets both esnext and es2020 as per PR comment Signed-off-by: Ashley Claymore <[email protected]> * switch to using a binary expression Signed-off-by: Ashley Claymore <[email protected]> * [squash] PrivateIdentifier now extends PrimaryExpression Signed-off-by: Ashley Claymore <[email protected]> * [squash] accept public api baseline changes Signed-off-by: Ashley Claymore <[email protected]> * [squash] classPrivateFieldInHelper now has documentation Signed-off-by: Ashley Claymore <[email protected]> * [squash] type-check now follows existing in-expression path Signed-off-by: Ashley Claymore <[email protected]> * [squash] parser now follows existing binaryExpression path Signed-off-by: Ashley Claymore <[email protected]> * [squash] correct typo in comment Signed-off-by: Ashley Claymore <[email protected]> * [squash] no longer use esNext flag Signed-off-by: Ashley Claymore <[email protected]> * [squash] swap 'reciever, state' helper params Signed-off-by: Ashley Claymore <[email protected]> * [squash] remove change to parenthesizerRules Signed-off-by: Ashley Claymore <[email protected]> * [squash] apply suggested changes to checker Signed-off-by: Ashley Claymore <[email protected]> * [squash] remove need for assertion in fixSpelling Signed-off-by: Ashley Claymore <[email protected]> * [squash] improve comment hint in test Signed-off-by: Ashley Claymore <[email protected]> * [squash] fix comment typos Signed-off-by: Ashley Claymore <[email protected]> * [squash] add flow-test for Foo | FooSub | Bar Signed-off-by: Ashley Claymore <[email protected]> * [squash] add checkExternalEmitHelpers call and new test case Signed-off-by: Ashley Claymore <[email protected]> * [squash] simplify and correct parser Signed-off-by: Ashley Claymore <[email protected]> * [squash] move most of the added checker logic to expression level Signed-off-by: Ashley Claymore <[email protected]> * [squash] always error when privateId could not be resolved Signed-off-by: Ashley Claymore <[email protected]> * [squash] reword comment Signed-off-by: Ashley Claymore <[email protected]> * [squash] fix codeFixSpelling test Signed-off-by: Ashley Claymore <[email protected]> * [squash] do less work Signed-off-by: Ashley Claymore <[email protected]> * store symbol by priateId not binaryExpression Signed-off-by: Ashley Claymore <[email protected]> * moved parsePrivateIdentifier into parsePrimaryExpression Signed-off-by: Ashley Claymore <[email protected]> * [squash] checkInExpressionn bails out early on silentNeverType Signed-off-by: Ashley Claymore <[email protected]> * [squash] more detailed error messages Signed-off-by: Ashley Claymore <[email protected]> * [squash] resolves conflict in diagnosticMessages.json Signed-off-by: Ashley Claymore <[email protected]> * [squash] update baseline for importHelpersES6 Signed-off-by: Ashley Claymore <[email protected]> * [squash] remove redundent if and comment from parser Signed-off-by: Ashley Claymore <[email protected]> * [squash] split up grammar/check/symbolLookup Signed-off-by: Ashley Claymore <[email protected]> * [squash] reword message for existing left side of in-expression error Signed-off-by: Ashley Claymore <[email protected]>
*Issue number of the reported bug or feature request: *
microsoft#42574
Describe your changes
Implements the stage 3 'private-fields-in-in' proposal
https://github.com/tc39/proposal-private-fields-in-in
WIP Progress
'prop' in objis a BinaryExpression, but this isn't because syntactically the left side can only be aPrivateIdentifierPrivateIdentifiercan't be looked-up symbolicallyboolean'prop' in obj#