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
[ts] Add support for instantiation expressions #14457
[ts] Add support for instantiation expressions #14457
Conversation
nicolo-ribaudo
commented
Apr 12, 2022
•
edited by gitpod-io
bot
edited by gitpod-io
bot
Q | A |
---|---|
Fixed Issues? | Fixes #14322 |
Patch: Bug Fix? | |
Major: Breaking Change? | |
Minor: New Feature? | Y |
Tests Added + Pass? | Yes |
Documentation PR Link | |
Any Dependency Changes? | |
License | MIT |
); | ||
result.typeParameters = typeArguments; | ||
return result; | ||
if (tokenIsTemplate(this.state.type)) { |
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 code has just been moved.
@@ -1842,6 +1842,21 @@ export default class ExpressionParser extends LValParser { | |||
// argument list. | |||
// https://tc39.es/ecma262/#prod-NewExpression | |||
parseNew(node: N.Expression): N.NewExpression { | |||
this.parseNewCallee(node); | |||
|
|||
if (this.eat(tt.parenL)) { |
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 code has just been moved.
// a<b> | ||
// if (0); | ||
// is not valid TS code (https://github.com/microsoft/TypeScript/issues/48654) | ||
// However, it should correctly parse anything that is correctly parsed by TS. |
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.
Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/51950/ |
parent: t.Node, | ||
) { | ||
return ( | ||
(isCallExpression(parent) || |
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.
Will parent
be an OptionalCallExpression?
|
||
(a<b>)<c>; | ||
(a<b>)<c>(); | ||
new (a<b>)<c>(); |
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.
Can you add
(a<b>)<c>?.();
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.
- We may need to mark
TSExpressionWithTypeArguments
as an expression: Babel is throwing Property right of AssignmentExpression expected node to be of a type ["Expression"] but instead got "TSExpressionWithTypeArguments" when transpiling both TS and optional chaining (REPL):
a<b>?.();
Behaviour differences:
- Babel accepts
a<b><c>();
while TS rejects it with "Expression expected" - Babel rejects
let a: typeof a<b>
while TS accepts it - Babel parses
a>b<x>>1
as TSExpressionWithTypeArguments while TS parses them as JS.
I just realized that
I'm tempted to update this PR to use a new node, such as |
Regardless of whether we are adding a new node for Instantiation Expression, we should extend |
@nicolo-ribaudo are you sure this is correct? declare function foo(): <T>() => T;
let a3 = foo()<string>;
// ^^^^^^^^^^^^^ ExpressionWithTypeArguments Based on TS's defined types the expression is any valid LHS expression: |
However, |
Yeah TS uses the same node for all those usecase then blocks weird cases like that with a semantic error. In our AST we have a separate node for that usecase - I think class implements also uses the same node and the same parsing logic within TS. Again in our AST we have a special node for this - So it's fine for us to treat the new node differently. Side note - I wish TS was stricter with its parsing in these cases. Allowing so much and relying on semantic validation to narrow the scope feels so dirty. |
Non-fun fact: in |
So, there are multiple possible options:
My preference is 3/4 first, then 2 and then 1. I think using the same node for both (like TS does) is particularly confusing because:
@JLHwung @existentialism WDYT? |
It's also possible that in Babel 8 we will be able to just use |
@nicolo-ribaudo I agree with approach 4. I support renaming |
A consideration for why the AST is designed the way it is; it's designed to support TS's AST shape - which in turn is designed to support the "recoverable parsing" in a type-safe manner. So the reason that we use special nodes for these is because it's another one of those "TS uses semantic errors to ban weird syntax" things. (2) would align your AST shape with the typescript-eslint estree spec as it has been defined since the beginning. I could acquiesce to naming it |
@bradzacher Another reason I like |
Also, do you plan to represent I am currently implementing it in the first way (similarly to how tsc does), but I'm annoyed because |
Interestingly I found #12884 which logs the difference in our ASTs for the heritage nodes!
When I saw @sosukesuzuki mention this above - I didn't hugely love the idea because it means that However then I looked into it and noticed that in our AST - Looking into it more, for I don't know exactly what the better option is here. i.e. I think this is the best AST type we can define interface TSTypeQuery {
exprName: Identifier | TSQualifiedName | TSImportType;
typeParameters?: TSTypeParameterInstantiation;
} if you wanted you could be fancy and define a union type for it? interface TSTypeQueryWithImport {
exprName: TSImportType;
typeParameters?: never;
}
interface TSTypeQueryWithName {
exprName: Identifier | TSQualifiedName;
typeParameters?: TSTypeParameterInstantiation;
}
type TSTypeQuery = TSTypeQueryWithImport | TSTypeQueryWithName; We've done similar sorts of magic to properly define types for things like properties and the differences between computed and non-computed keys. |
I second @JLHwung, (4) 👍 |
@bradzacher It's getting a bit out-of-scope for this PR (we might then migrate the discussion somewhere else), but since both your and our projects are considering AST breaking changes for the next major, we could make it a bit change and completely restructure the AST to something sensible like this: // typeof ...
interface TSTypeQuery {
typeAnnotation: TSImportType | TSTypeParameterInstantiation | TSQualifiedName | Identifier;
}
// ...<x, y, z>
interface TSTypeParameterInstantiation {
typeName: TSQualifiedName | Identifier;
typeParameters: Array<TSType>;
}
// ... .x
interface TSQualifiedName {
left: TSQualifiedName | TSImportType | Identifier;
right: Identifier;
}
// import("...")
interface TSImportType {
argument: StringLiteral;
} By doing so, type parameters are only represented in
This also follows the AST design for JS nodes:
EDIT: to de-dupe even further the number of places where you can find type parameters in the AST, we could also represent |
Co-authored-by: Brad Zacher <brad.zacher@gmail.com>
fc9cb52
to
d67183b
Compare