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
Changes from all commits
5232c0d
fc5cc65
8ce39af
44ab46b
677b74d
93e2b14
12ff118
f053712
938bce9
48c534c
9db01e2
3375a9f
10e39ee
b7e1493
568ea92
d67183b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
a<b>; | ||
|
||
(a<b>)<c>; | ||
(a<b>)<c>(); | ||
(a<b>)<c>?.(); | ||
(a?.b<c>)<d>(); | ||
new (a<b>)<c>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add (a<b>)<c>?.(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
a<b>; | ||
(a<b>)<c>; | ||
(a<b>)<c>(); | ||
(a<b>)<c>?.(); | ||
(a?.b<c>)<d>(); | ||
new (a<b>)<c>(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
new C<T>(); | ||
new C<T, U>(); | ||
new C<T>; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
new C<T>(); | ||
new C<T, U>(); | ||
new C<T, U>(); | ||
new C<T>(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
let x: typeof y.z; | ||
let a: typeof b.c<d>; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
let x: typeof y.z; | ||
let x: typeof y.z; | ||
let a: typeof b.c<d>; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1842,7 +1842,22 @@ export default class ExpressionParser extends LValParser { | |
// argument to parseSubscripts to prevent it from consuming the | ||
// argument list. | ||
// https://tc39.es/ecma262/#prod-NewExpression | ||
parseNew(node: N.Expression): N.NewExpression { | ||
parseNew(node: N.NewExpression): N.NewExpression { | ||
this.parseNewCallee(node); | ||
|
||
if (this.eat(tt.parenL)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code has just been moved. |
||
const args = this.parseExprList(tt.parenR); | ||
this.toReferencedList(args); | ||
// $FlowFixMe (parseExprList should be all non-null in this case) | ||
node.arguments = args; | ||
} else { | ||
node.arguments = []; | ||
} | ||
|
||
return this.finishNode(node, "NewExpression"); | ||
} | ||
|
||
parseNewCallee(node: N.NewExpression): void { | ||
node.callee = this.parseNoCallExpr(); | ||
if (node.callee.type === "Import") { | ||
this.raise(Errors.ImportCallNotNewExpression, { at: node.callee }); | ||
|
@@ -1855,20 +1870,6 @@ export default class ExpressionParser extends LValParser { | |
at: this.state.startLoc, | ||
}); | ||
} | ||
|
||
this.parseNewArguments(node); | ||
return this.finishNode(node, "NewExpression"); | ||
} | ||
|
||
parseNewArguments(node: N.NewExpression): void { | ||
if (this.eat(tt.parenL)) { | ||
const args = this.parseExprList(tt.parenR); | ||
this.toReferencedList(args); | ||
// $FlowFixMe (parseExprList should be all non-null in this case) | ||
node.arguments = args; | ||
} else { | ||
node.arguments = []; | ||
} | ||
} | ||
|
||
// Parse template expression. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,8 @@ import { | |
tt, | ||
type TokenType, | ||
tokenIsTemplate, | ||
tokenCanStartExpression, | ||
tokenIsBinaryOperator, | ||
} from "../../tokenizer/types"; | ||
import { types as tc } from "../../tokenizer/context"; | ||
import * as N from "../../types"; | ||
|
@@ -63,6 +65,12 @@ function assert(x: boolean): void { | |
} | ||
} | ||
|
||
function tsTokenCanStartExpression(token: TokenType) { | ||
// tsc considers binary operators as "can start expression" tokens: | ||
// https://github.com/microsoft/TypeScript/blob/eca1b4/src/compiler/parser.ts#L4260-L4266 | ||
return tokenCanStartExpression(token) || tokenIsBinaryOperator(token); | ||
} | ||
|
||
type ParsingContext = | ||
| "EnumMembers" | ||
| "HeritageClauseElement" | ||
|
@@ -611,6 +619,9 @@ export default (superClass: Class<Parser>): Class<Parser> => | |
} else { | ||
node.exprName = this.tsParseEntityName(); | ||
} | ||
if (!this.hasPrecedingLineBreak() && this.match(tt.lt)) { | ||
node.typeParameters = this.tsParseTypeArguments(); | ||
} | ||
return this.finishNode(node, "TSTypeQuery"); | ||
} | ||
|
||
|
@@ -1561,7 +1572,15 @@ export default (superClass: Class<Parser>): Class<Parser> => | |
|
||
const delimitedList = this.tsParseDelimitedList( | ||
"HeritageClauseElement", | ||
this.tsParseExpressionWithTypeArguments.bind(this), | ||
() => { | ||
const node: N.TsExpressionWithTypeArguments = this.startNode(); | ||
node.expression = this.tsParseEntityName(); | ||
if (this.match(tt.lt)) { | ||
node.typeParameters = this.tsParseTypeArguments(); | ||
} | ||
|
||
return this.finishNode(node, "TSExpressionWithTypeArguments"); | ||
}, | ||
); | ||
|
||
if (!delimitedList.length) { | ||
|
@@ -1574,16 +1593,6 @@ export default (superClass: Class<Parser>): Class<Parser> => | |
return delimitedList; | ||
} | ||
|
||
tsParseExpressionWithTypeArguments(): N.TsExpressionWithTypeArguments { | ||
const node: N.TsExpressionWithTypeArguments = this.startNode(); | ||
node.expression = this.tsParseEntityName(); | ||
if (this.match(tt.lt)) { | ||
node.typeParameters = this.tsParseTypeArguments(); | ||
} | ||
|
||
return this.finishNode(node, "TSExpressionWithTypeArguments"); | ||
} | ||
|
||
tsParseInterfaceDeclaration( | ||
node: N.TsInterfaceDeclaration, | ||
properties: { declare?: true } = {}, | ||
|
@@ -2294,48 +2303,69 @@ export default (superClass: Class<Parser>): Class<Parser> => | |
} | ||
} | ||
|
||
const node: N.CallExpression = this.startNodeAt(startPos, startLoc); | ||
node.callee = base; | ||
|
||
const typeArguments = this.tsParseTypeArgumentsInExpression(); | ||
if (!typeArguments) throw this.unexpected(); | ||
|
||
if (typeArguments) { | ||
if (isOptionalCall && !this.match(tt.parenL)) { | ||
missingParenErrorLoc = this.state.curPosition(); | ||
this.unexpected(); | ||
} | ||
if (isOptionalCall && !this.match(tt.parenL)) { | ||
missingParenErrorLoc = this.state.curPosition(); | ||
throw this.unexpected(); | ||
} | ||
|
||
if (tokenIsTemplate(this.state.type)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code has just been moved. |
||
const result = this.parseTaggedTemplateExpression( | ||
base, | ||
startPos, | ||
startLoc, | ||
state, | ||
); | ||
result.typeParameters = typeArguments; | ||
return result; | ||
} | ||
|
||
if (!noCalls && this.eat(tt.parenL)) { | ||
const node: N.CallExpression = this.startNodeAt(startPos, startLoc); | ||
node.callee = base; | ||
// possibleAsync always false here, because we would have handled it above. | ||
// $FlowIgnore (won't be any undefined arguments) | ||
node.arguments = this.parseCallExpressionArguments( | ||
tt.parenR, | ||
/* possibleAsync */ false, | ||
); | ||
|
||
if (!noCalls && this.eat(tt.parenL)) { | ||
// possibleAsync always false here, because we would have handled it above. | ||
// $FlowIgnore (won't be any undefined arguments) | ||
node.arguments = this.parseCallExpressionArguments( | ||
tt.parenR, | ||
/* possibleAsync */ false, | ||
); | ||
|
||
// Handles invalid case: `f<T>(a:b)` | ||
this.tsCheckForInvalidTypeCasts(node.arguments); | ||
|
||
node.typeParameters = typeArguments; | ||
if (state.optionalChainMember) { | ||
// $FlowIgnore | ||
node.optional = isOptionalCall; | ||
} | ||
|
||
return this.finishCallExpression(node, state.optionalChainMember); | ||
} else if (tokenIsTemplate(this.state.type)) { | ||
const result = this.parseTaggedTemplateExpression( | ||
base, | ||
startPos, | ||
startLoc, | ||
state, | ||
); | ||
result.typeParameters = typeArguments; | ||
return result; | ||
// Handles invalid case: `f<T>(a:b)` | ||
this.tsCheckForInvalidTypeCasts(node.arguments); | ||
|
||
node.typeParameters = typeArguments; | ||
if (state.optionalChainMember) { | ||
// $FlowIgnore | ||
node.optional = isOptionalCall; | ||
} | ||
|
||
return this.finishCallExpression(node, state.optionalChainMember); | ||
} | ||
|
||
// TODO: This doesn't exactly match what TS does when it comes to ASI. | ||
// For example, | ||
// 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 commentThe reason will be displayed to describe this comment to others. Learn more. |
||
if ( | ||
tsTokenCanStartExpression(this.state.type) && | ||
this.state.type !== tt.parenL | ||
) { | ||
// Bail out. We have something like a<b>c, which is not an expression with | ||
// type arguments but an (a < b) > c comparison. | ||
throw this.unexpected(); | ||
} | ||
|
||
this.unexpected(); | ||
const node: N.TsInstantiationExpression = this.startNodeAt( | ||
startPos, | ||
startLoc, | ||
); | ||
node.expression = base; | ||
node.typeParameters = typeArguments; | ||
return this.finishNode(node, "TSInstantiationExpression"); | ||
}); | ||
|
||
if (missingParenErrorLoc) { | ||
|
@@ -2348,22 +2378,17 @@ export default (superClass: Class<Parser>): Class<Parser> => | |
return super.parseSubscript(base, startPos, startLoc, noCalls, state); | ||
} | ||
|
||
parseNewArguments(node: N.NewExpression): void { | ||
// tsTryParseAndCatch is expensive, so avoid if not necessary. | ||
// 99% certain this is `new C<T>();`. But may be `new C < T;`, which is also legal. | ||
// Also handles `new C<<T>` | ||
if (this.match(tt.lt) || this.match(tt.bitShiftL)) { | ||
const typeParameters = this.tsTryParseAndCatch(() => { | ||
const args = this.tsParseTypeArgumentsInExpression(); | ||
if (!this.match(tt.parenL)) this.unexpected(); | ||
return args; | ||
}); | ||
if (typeParameters) { | ||
node.typeParameters = typeParameters; | ||
} | ||
} | ||
parseNewCallee(node: N.NewExpression): void { | ||
super.parseNewCallee(node); | ||
|
||
super.parseNewArguments(node); | ||
const { callee } = node; | ||
if ( | ||
callee.type === "TSInstantiationExpression" && | ||
!callee.extra?.parenthesized | ||
) { | ||
node.typeParameters = callee.typeParameters; | ||
node.callee = callee.expression; | ||
} | ||
} | ||
|
||
parseExprOp( | ||
|
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?