Skip to content

Commit 5afbfbc

Browse files
authored
Instantiation expressions (#47607)
* Permit type arguments in references to generic functions * Accept new baselines * Delete pointless fourslash test * Fix lint issue * Finalize implementation * Add tests * Accept new baselines * Properly handle instantiation of instantiation expression types * Accept new API baselines * Fix lint error * Add more tests * Properly handle unions/intersections of generic types * Add more tests * More permissive parsing of type arguments in member expressions * Update tests * Accept new baselines
1 parent b9a06e5 commit 5afbfbc

File tree

57 files changed

+2663
-354
lines changed

Some content is hidden

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

57 files changed

+2663
-354
lines changed

src/compiler/checker.ts

Lines changed: 146 additions & 69 deletions
Large diffs are not rendered by default.

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,10 +1152,6 @@
11521152
"category": "Error",
11531153
"code": 1383
11541154
},
1155-
"A 'new' expression with type arguments must always be followed by a parenthesized argument list.": {
1156-
"category": "Error",
1157-
"code": 1384
1158-
},
11591155
"Function type notation must be parenthesized when used in a union type.": {
11601156
"category": "Error",
11611157
"code": 1385
@@ -2703,6 +2699,10 @@
27032699
"category": "Error",
27042700
"code": 2634
27052701
},
2702+
"Type '{0}' has no signatures for which the type argument list is applicable.": {
2703+
"category": "Error",
2704+
"code": 2635
2705+
},
27062706

27072707
"Cannot augment module '{0}' with value exports because it resolves to a non-module entity.": {
27082708
"category": "Error",

src/compiler/emitter.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1727,6 +1727,8 @@ namespace ts {
17271727
return emitAsExpression(node as AsExpression);
17281728
case SyntaxKind.NonNullExpression:
17291729
return emitNonNullExpression(node as NonNullExpression);
1730+
case SyntaxKind.ExpressionWithTypeArguments:
1731+
return emitExpressionWithTypeArguments(node as ExpressionWithTypeArguments);
17301732
case SyntaxKind.MetaProperty:
17311733
return emitMetaProperty(node as MetaProperty);
17321734
case SyntaxKind.SyntheticExpression:
@@ -2227,6 +2229,7 @@ namespace ts {
22272229
writeKeyword("typeof");
22282230
writeSpace();
22292231
emit(node.exprName);
2232+
emitTypeArguments(node, node.typeArguments);
22302233
}
22312234

22322235
function emitTypeLiteral(node: TypeLiteralNode) {

src/compiler/factory/nodeFactory.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1839,17 +1839,19 @@ namespace ts {
18391839
}
18401840

18411841
// @api
1842-
function createTypeQueryNode(exprName: EntityName) {
1842+
function createTypeQueryNode(exprName: EntityName, typeArguments?: readonly TypeNode[]) {
18431843
const node = createBaseNode<TypeQueryNode>(SyntaxKind.TypeQuery);
18441844
node.exprName = exprName;
1845+
node.typeArguments = typeArguments && parenthesizerRules().parenthesizeTypeArguments(typeArguments);
18451846
node.transformFlags = TransformFlags.ContainsTypeScript;
18461847
return node;
18471848
}
18481849

18491850
// @api
1850-
function updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName) {
1851+
function updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName, typeArguments?: readonly TypeNode[]) {
18511852
return node.exprName !== exprName
1852-
? update(createTypeQueryNode(exprName), node)
1853+
|| node.typeArguments !== typeArguments
1854+
? update(createTypeQueryNode(exprName, typeArguments), node)
18531855
: node;
18541856
}
18551857

src/compiler/parser.ts

Lines changed: 60 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,8 @@ namespace ts {
180180
visitNode(cbNode, (node as TypePredicateNode).parameterName) ||
181181
visitNode(cbNode, (node as TypePredicateNode).type);
182182
case SyntaxKind.TypeQuery:
183-
return visitNode(cbNode, (node as TypeQueryNode).exprName);
183+
return visitNode(cbNode, (node as TypeQueryNode).exprName) ||
184+
visitNodes(cbNode, cbNodes, (node as TypeQueryNode).typeArguments);
184185
case SyntaxKind.TypeLiteral:
185186
return visitNodes(cbNode, cbNodes, (node as TypeLiteralNode).members);
186187
case SyntaxKind.ArrayType:
@@ -3078,7 +3079,9 @@ namespace ts {
30783079
function parseTypeQuery(): TypeQueryNode {
30793080
const pos = getNodePos();
30803081
parseExpected(SyntaxKind.TypeOfKeyword);
3081-
return finishNode(factory.createTypeQueryNode(parseEntityName(/*allowReservedWords*/ true, /*allowPrivateIdentifiers*/ true)), pos);
3082+
const entityName = parseEntityName(/*allowReservedWords*/ true, /*allowPrivateIdentifiers*/ true);
3083+
const typeArguments = tryParseTypeArguments();
3084+
return finishNode(factory.createTypeQueryNode(entityName, typeArguments), pos);
30823085
}
30833086

30843087
function parseTypeParameter(): TypeParameterDeclaration {
@@ -5428,23 +5431,33 @@ namespace ts {
54285431
continue;
54295432
}
54305433

5431-
if (!questionDotToken && token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) {
5432-
nextToken();
5433-
expression = finishNode(factory.createNonNullExpression(expression), pos);
5434-
continue;
5435-
}
5436-
54375434
// when in the [Decorator] context, we do not parse ElementAccess as it could be part of a ComputedPropertyName
54385435
if ((questionDotToken || !inDecoratorContext()) && parseOptional(SyntaxKind.OpenBracketToken)) {
54395436
expression = parseElementAccessExpressionRest(pos, expression, questionDotToken);
54405437
continue;
54415438
}
54425439

54435440
if (isTemplateStartOfTaggedTemplate()) {
5444-
expression = parseTaggedTemplateRest(pos, expression, questionDotToken, /*typeArguments*/ undefined);
5441+
// Absorb type arguments into TemplateExpression when preceding expression is ExpressionWithTypeArguments
5442+
expression = !questionDotToken && expression.kind === SyntaxKind.ExpressionWithTypeArguments ?
5443+
parseTaggedTemplateRest(pos, (expression as ExpressionWithTypeArguments).expression, questionDotToken, (expression as ExpressionWithTypeArguments).typeArguments) :
5444+
parseTaggedTemplateRest(pos, expression, questionDotToken, /*typeArguments*/ undefined);
54455445
continue;
54465446
}
54475447

5448+
if (!questionDotToken) {
5449+
if (token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) {
5450+
nextToken();
5451+
expression = finishNode(factory.createNonNullExpression(expression), pos);
5452+
continue;
5453+
}
5454+
const typeArguments = tryParse(parseTypeArgumentsInExpression);
5455+
if (typeArguments) {
5456+
expression = finishNode(factory.createExpressionWithTypeArguments(expression, typeArguments), pos);
5457+
continue;
5458+
}
5459+
}
5460+
54485461
return expression as MemberExpression;
54495462
}
54505463
}
@@ -5471,39 +5484,30 @@ namespace ts {
54715484
function parseCallExpressionRest(pos: number, expression: LeftHandSideExpression): LeftHandSideExpression {
54725485
while (true) {
54735486
expression = parseMemberExpressionRest(pos, expression, /*allowOptionalChain*/ true);
5487+
let typeArguments: NodeArray<TypeNode> | undefined;
54745488
const questionDotToken = parseOptionalToken(SyntaxKind.QuestionDotToken);
5475-
// handle 'foo<<T>()'
5476-
// parse template arguments only in TypeScript files (not in JavaScript files).
5477-
if ((contextFlags & NodeFlags.JavaScriptFile) === 0 && (token() === SyntaxKind.LessThanToken || token() === SyntaxKind.LessThanLessThanToken)) {
5478-
// See if this is the start of a generic invocation. If so, consume it and
5479-
// keep checking for postfix expressions. Otherwise, it's just a '<' that's
5480-
// part of an arithmetic expression. Break out so we consume it higher in the
5481-
// stack.
5482-
const typeArguments = tryParse(parseTypeArgumentsInExpression);
5483-
if (typeArguments) {
5484-
if (isTemplateStartOfTaggedTemplate()) {
5485-
expression = parseTaggedTemplateRest(pos, expression, questionDotToken, typeArguments);
5486-
continue;
5487-
}
5488-
5489-
const argumentList = parseArgumentList();
5490-
const callExpr = questionDotToken || tryReparseOptionalChain(expression) ?
5491-
factory.createCallChain(expression, questionDotToken, typeArguments, argumentList) :
5492-
factory.createCallExpression(expression, typeArguments, argumentList);
5493-
expression = finishNode(callExpr, pos);
5489+
if (questionDotToken) {
5490+
typeArguments = tryParse(parseTypeArgumentsInExpression);
5491+
if (isTemplateStartOfTaggedTemplate()) {
5492+
expression = parseTaggedTemplateRest(pos, expression, questionDotToken, typeArguments);
54945493
continue;
54955494
}
54965495
}
5497-
else if (token() === SyntaxKind.OpenParenToken) {
5496+
if (typeArguments || token() === SyntaxKind.OpenParenToken) {
5497+
// Absorb type arguments into CallExpression when preceding expression is ExpressionWithTypeArguments
5498+
if (!questionDotToken && expression.kind === SyntaxKind.ExpressionWithTypeArguments) {
5499+
typeArguments = (expression as ExpressionWithTypeArguments).typeArguments;
5500+
expression = (expression as ExpressionWithTypeArguments).expression;
5501+
}
54985502
const argumentList = parseArgumentList();
54995503
const callExpr = questionDotToken || tryReparseOptionalChain(expression) ?
5500-
factory.createCallChain(expression, questionDotToken, /*typeArguments*/ undefined, argumentList) :
5501-
factory.createCallExpression(expression, /*typeArguments*/ undefined, argumentList);
5504+
factory.createCallChain(expression, questionDotToken, typeArguments, argumentList) :
5505+
factory.createCallExpression(expression, typeArguments, argumentList);
55025506
expression = finishNode(callExpr, pos);
55035507
continue;
55045508
}
55055509
if (questionDotToken) {
5506-
// We failed to parse anything, so report a missing identifier here.
5510+
// We parsed `?.` but then failed to parse anything, so report a missing identifier here.
55075511
const name = createMissingNode<Identifier>(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ false, Diagnostics.Identifier_expected);
55085512
expression = finishNode(factory.createPropertyAccessChain(expression, questionDotToken, name), pos);
55095513
}
@@ -5536,22 +5540,26 @@ namespace ts {
55365540
return undefined;
55375541
}
55385542

5539-
// If we have a '<', then only parse this as a argument list if the type arguments
5540-
// are complete and we have an open paren. if we don't, rewind and return nothing.
5541-
return typeArguments && canFollowTypeArgumentsInExpression()
5542-
? typeArguments
5543-
: undefined;
5543+
// We successfully parsed a type argument list. The next token determines whether we want to
5544+
// treat it as such. If the type argument list is followed by `(` or a template literal, as in
5545+
// `f<number>(42)`, we favor the type argument interpretation even though JavaScript would view
5546+
// it as a relational expression.
5547+
return typeArguments && canFollowTypeArgumentsInExpression() ? typeArguments : undefined;
55445548
}
55455549

55465550
function canFollowTypeArgumentsInExpression(): boolean {
55475551
switch (token()) {
5552+
// These tokens can follow a type argument list in a call expression.
55485553
case SyntaxKind.OpenParenToken: // foo<x>(
55495554
case SyntaxKind.NoSubstitutionTemplateLiteral: // foo<T> `...`
55505555
case SyntaxKind.TemplateHead: // foo<T> `...${100}...`
5551-
// these are the only tokens can legally follow a type argument
5552-
// list. So we definitely want to treat them as type arg lists.
5556+
// These tokens can't follow in a call expression, nor can they start an
5557+
// expression. So, consider the type argument list part of an instantiation
5558+
// expression.
55535559
// falls through
5560+
case SyntaxKind.CommaToken: // foo<x>,
55545561
case SyntaxKind.DotToken: // foo<x>.
5562+
case SyntaxKind.QuestionDotToken: // foo<x>?.
55555563
case SyntaxKind.CloseParenToken: // foo<x>)
55565564
case SyntaxKind.CloseBracketToken: // foo<x>]
55575565
case SyntaxKind.ColonToken: // foo<x>:
@@ -5569,21 +5577,10 @@ namespace ts {
55695577
case SyntaxKind.BarToken: // foo<x> |
55705578
case SyntaxKind.CloseBraceToken: // foo<x> }
55715579
case SyntaxKind.EndOfFileToken: // foo<x>
5572-
// these cases can't legally follow a type arg list. However, they're not legal
5573-
// expressions either. The user is probably in the middle of a generic type. So
5574-
// treat it as such.
55755580
return true;
5576-
5577-
case SyntaxKind.CommaToken: // foo<x>,
5578-
case SyntaxKind.OpenBraceToken: // foo<x> {
5579-
// We don't want to treat these as type arguments. Otherwise we'll parse this
5580-
// as an invocation expression. Instead, we want to parse out the expression
5581-
// in isolation from the type arguments.
5582-
// falls through
5583-
default:
5584-
// Anything else treat as an expression.
5585-
return false;
55865581
}
5582+
// Treat anything else as an expression.
5583+
return false;
55875584
}
55885585

55895586
function parsePrimaryExpression(): PrimaryExpression {
@@ -5790,30 +5787,16 @@ namespace ts {
57905787
const name = parseIdentifierName();
57915788
return finishNode(factory.createMetaProperty(SyntaxKind.NewKeyword, name), pos);
57925789
}
5793-
57945790
const expressionPos = getNodePos();
5795-
let expression: MemberExpression = parsePrimaryExpression();
5796-
let typeArguments;
5797-
while (true) {
5798-
expression = parseMemberExpressionRest(expressionPos, expression, /*allowOptionalChain*/ false);
5799-
typeArguments = tryParse(parseTypeArgumentsInExpression);
5800-
if (isTemplateStartOfTaggedTemplate()) {
5801-
Debug.assert(!!typeArguments,
5802-
"Expected a type argument list; all plain tagged template starts should be consumed in 'parseMemberExpressionRest'");
5803-
expression = parseTaggedTemplateRest(expressionPos, expression, /*optionalChain*/ undefined, typeArguments);
5804-
typeArguments = undefined;
5805-
}
5806-
break;
5791+
let expression: LeftHandSideExpression = parseMemberExpressionRest(expressionPos, parsePrimaryExpression(), /*allowOptionalChain*/ false);
5792+
let typeArguments: NodeArray<TypeNode> | undefined;
5793+
// Absorb type arguments into NewExpression when preceding expression is ExpressionWithTypeArguments
5794+
if (expression.kind === SyntaxKind.ExpressionWithTypeArguments) {
5795+
typeArguments = (expression as ExpressionWithTypeArguments).typeArguments;
5796+
expression = (expression as ExpressionWithTypeArguments).expression;
58075797
}
5808-
5809-
let argumentsArray: NodeArray<Expression> | undefined;
5810-
if (token() === SyntaxKind.OpenParenToken) {
5811-
argumentsArray = parseArgumentList();
5812-
}
5813-
else if (typeArguments) {
5814-
parseErrorAt(pos, scanner.getStartPos(), Diagnostics.A_new_expression_with_type_arguments_must_always_be_followed_by_a_parenthesized_argument_list);
5815-
}
5816-
return finishNode(factory.createNewExpression(expression, typeArguments, argumentsArray), pos);
5798+
const argumentList = token() === SyntaxKind.OpenParenToken ? parseArgumentList() : undefined;
5799+
return finishNode(factory.createNewExpression(expression, typeArguments, argumentList), pos);
58175800
}
58185801

58195802
// STATEMENTS
@@ -7071,6 +7054,9 @@ namespace ts {
70717054
function parseExpressionWithTypeArguments(): ExpressionWithTypeArguments {
70727055
const pos = getNodePos();
70737056
const expression = parseLeftHandSideExpressionOrHigher();
7057+
if (expression.kind === SyntaxKind.ExpressionWithTypeArguments) {
7058+
return expression as ExpressionWithTypeArguments;
7059+
}
70747060
const typeArguments = tryParseTypeArguments();
70757061
return finishNode(factory.createExpressionWithTypeArguments(expression, typeArguments), pos);
70767062
}

0 commit comments

Comments
 (0)