Skip to content
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

Instantiation expressions #47607

Merged
merged 17 commits into from
Feb 16, 2022
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 146 additions & 69 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1140,10 +1140,6 @@
"category": "Error",
"code": 1383
},
"A 'new' expression with type arguments must always be followed by a parenthesized argument list.": {
"category": "Error",
"code": 1384
},
"Function type notation must be parenthesized when used in a union type.": {
"category": "Error",
"code": 1385
Expand Down Expand Up @@ -2671,6 +2667,10 @@
"category": "Error",
"code": 2634
},
"Type '{0}' has no signatures for which the type argument list is applicable.": {
"category": "Error",
"code": 2635
},

"Cannot augment module '{0}' with value exports because it resolves to a non-module entity.": {
"category": "Error",
Expand Down
3 changes: 3 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1727,6 +1727,8 @@ namespace ts {
return emitAsExpression(node as AsExpression);
case SyntaxKind.NonNullExpression:
return emitNonNullExpression(node as NonNullExpression);
case SyntaxKind.ExpressionWithTypeArguments:
return emitExpressionWithTypeArguments(node as ExpressionWithTypeArguments);
case SyntaxKind.MetaProperty:
return emitMetaProperty(node as MetaProperty);
case SyntaxKind.SyntheticExpression:
Expand Down Expand Up @@ -2227,6 +2229,7 @@ namespace ts {
writeKeyword("typeof");
writeSpace();
emit(node.exprName);
emitTypeArguments(node, node.typeArguments);
}

function emitTypeLiteral(node: TypeLiteralNode) {
Expand Down
8 changes: 5 additions & 3 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1839,17 +1839,19 @@ namespace ts {
}

// @api
function createTypeQueryNode(exprName: EntityName) {
function createTypeQueryNode(exprName: EntityName, typeArguments?: readonly TypeNode[]) {
const node = createBaseNode<TypeQueryNode>(SyntaxKind.TypeQuery);
node.exprName = exprName;
node.typeArguments = typeArguments && parenthesizerRules().parenthesizeTypeArguments(typeArguments);
node.transformFlags = TransformFlags.ContainsTypeScript;
return node;
}

// @api
function updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName) {
function updateTypeQueryNode(node: TypeQueryNode, exprName: EntityName, typeArguments?: readonly TypeNode[]) {
return node.exprName !== exprName
? update(createTypeQueryNode(exprName), node)
|| node.typeArguments !== typeArguments
? update(createTypeQueryNode(exprName, typeArguments), node)
: node;
}

Expand Down
134 changes: 60 additions & 74 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,8 @@ namespace ts {
visitNode(cbNode, (node as TypePredicateNode).parameterName) ||
visitNode(cbNode, (node as TypePredicateNode).type);
case SyntaxKind.TypeQuery:
return visitNode(cbNode, (node as TypeQueryNode).exprName);
return visitNode(cbNode, (node as TypeQueryNode).exprName) ||
visitNodes(cbNode, cbNodes, (node as TypeQueryNode).typeArguments);
case SyntaxKind.TypeLiteral:
return visitNodes(cbNode, cbNodes, (node as TypeLiteralNode).members);
case SyntaxKind.ArrayType:
Expand Down Expand Up @@ -3078,7 +3079,9 @@ namespace ts {
function parseTypeQuery(): TypeQueryNode {
const pos = getNodePos();
parseExpected(SyntaxKind.TypeOfKeyword);
return finishNode(factory.createTypeQueryNode(parseEntityName(/*allowReservedWords*/ true)), pos);
const entityName = parseEntityName(/*allowReservedWords*/ true);
const typeArguments = tryParseTypeArguments();
return finishNode(factory.createTypeQueryNode(entityName, typeArguments), pos);
}

function parseTypeParameter(): TypeParameterDeclaration {
Expand Down Expand Up @@ -5428,23 +5431,33 @@ namespace ts {
continue;
}

if (!questionDotToken && token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) {
nextToken();
expression = finishNode(factory.createNonNullExpression(expression), pos);
continue;
}

// when in the [Decorator] context, we do not parse ElementAccess as it could be part of a ComputedPropertyName
if ((questionDotToken || !inDecoratorContext()) && parseOptional(SyntaxKind.OpenBracketToken)) {
expression = parseElementAccessExpressionRest(pos, expression, questionDotToken);
continue;
}

if (isTemplateStartOfTaggedTemplate()) {
expression = parseTaggedTemplateRest(pos, expression, questionDotToken, /*typeArguments*/ undefined);
// Absorb type arguments into TemplateExpression when preceding expression is ExpressionWithTypeArguments
expression = !questionDotToken && expression.kind === SyntaxKind.ExpressionWithTypeArguments ?
parseTaggedTemplateRest(pos, (expression as ExpressionWithTypeArguments).expression, questionDotToken, (expression as ExpressionWithTypeArguments).typeArguments) :
parseTaggedTemplateRest(pos, expression, questionDotToken, /*typeArguments*/ undefined);
continue;
}

if (!questionDotToken) {
if (token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) {
nextToken();
expression = finishNode(factory.createNonNullExpression(expression), pos);
continue;
}
const typeArguments = tryParse(parseTypeArgumentsInExpression);
if (typeArguments) {
expression = finishNode(factory.createExpressionWithTypeArguments(expression, typeArguments), pos);
continue;
}
}

return expression as MemberExpression;
}
}
Expand All @@ -5471,39 +5484,30 @@ namespace ts {
function parseCallExpressionRest(pos: number, expression: LeftHandSideExpression): LeftHandSideExpression {
while (true) {
expression = parseMemberExpressionRest(pos, expression, /*allowOptionalChain*/ true);
let typeArguments: NodeArray<TypeNode> | undefined;
const questionDotToken = parseOptionalToken(SyntaxKind.QuestionDotToken);
// handle 'foo<<T>()'
// parse template arguments only in TypeScript files (not in JavaScript files).
if ((contextFlags & NodeFlags.JavaScriptFile) === 0 && (token() === SyntaxKind.LessThanToken || token() === SyntaxKind.LessThanLessThanToken)) {
// See if this is the start of a generic invocation. If so, consume it and
// keep checking for postfix expressions. Otherwise, it's just a '<' that's
// part of an arithmetic expression. Break out so we consume it higher in the
// stack.
const typeArguments = tryParse(parseTypeArgumentsInExpression);
if (typeArguments) {
if (isTemplateStartOfTaggedTemplate()) {
expression = parseTaggedTemplateRest(pos, expression, questionDotToken, typeArguments);
continue;
}

const argumentList = parseArgumentList();
const callExpr = questionDotToken || tryReparseOptionalChain(expression) ?
factory.createCallChain(expression, questionDotToken, typeArguments, argumentList) :
factory.createCallExpression(expression, typeArguments, argumentList);
expression = finishNode(callExpr, pos);
if (questionDotToken) {
typeArguments = tryParse(parseTypeArgumentsInExpression);
if (isTemplateStartOfTaggedTemplate()) {
expression = parseTaggedTemplateRest(pos, expression, questionDotToken, typeArguments);
continue;
}
}
else if (token() === SyntaxKind.OpenParenToken) {
if (typeArguments || token() === SyntaxKind.OpenParenToken) {
// Absorb type arguments into CallExpression when preceding expression is ExpressionWithTypeArguments
if (!questionDotToken && expression.kind === SyntaxKind.ExpressionWithTypeArguments) {
typeArguments = (expression as ExpressionWithTypeArguments).typeArguments;
expression = (expression as ExpressionWithTypeArguments).expression;
}
const argumentList = parseArgumentList();
const callExpr = questionDotToken || tryReparseOptionalChain(expression) ?
factory.createCallChain(expression, questionDotToken, /*typeArguments*/ undefined, argumentList) :
factory.createCallExpression(expression, /*typeArguments*/ undefined, argumentList);
factory.createCallChain(expression, questionDotToken, typeArguments, argumentList) :
factory.createCallExpression(expression, typeArguments, argumentList);
expression = finishNode(callExpr, pos);
continue;
}
if (questionDotToken) {
// We failed to parse anything, so report a missing identifier here.
// We parsed `?.` but then failed to parse anything, so report a missing identifier here.
const name = createMissingNode<Identifier>(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ false, Diagnostics.Identifier_expected);
expression = finishNode(factory.createPropertyAccessChain(expression, questionDotToken, name), pos);
}
Expand Down Expand Up @@ -5536,22 +5540,26 @@ namespace ts {
return undefined;
}

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

function canFollowTypeArgumentsInExpression(): boolean {
switch (token()) {
// These tokens can follow a type argument list in a call expression.
case SyntaxKind.OpenParenToken: // foo<x>(
case SyntaxKind.NoSubstitutionTemplateLiteral: // foo<T> `...`
case SyntaxKind.TemplateHead: // foo<T> `...${100}...`
// these are the only tokens can legally follow a type argument
// list. So we definitely want to treat them as type arg lists.
// These tokens can't follow in a call expression, nor can they start an
// expression. So, consider the type argument list part of an instantiation
// expression.
// falls through
case SyntaxKind.CommaToken: // foo<x>,
case SyntaxKind.DotToken: // foo<x>.
case SyntaxKind.QuestionDotToken: // foo<x>?.
case SyntaxKind.CloseParenToken: // foo<x>)
case SyntaxKind.CloseBracketToken: // foo<x>]
case SyntaxKind.ColonToken: // foo<x>:
Expand All @@ -5569,21 +5577,10 @@ namespace ts {
case SyntaxKind.BarToken: // foo<x> |
case SyntaxKind.CloseBraceToken: // foo<x> }
case SyntaxKind.EndOfFileToken: // foo<x>
// these cases can't legally follow a type arg list. However, they're not legal
// expressions either. The user is probably in the middle of a generic type. So
// treat it as such.
return true;

case SyntaxKind.CommaToken: // foo<x>,
case SyntaxKind.OpenBraceToken: // foo<x> {
// We don't want to treat these as type arguments. Otherwise we'll parse this
// as an invocation expression. Instead, we want to parse out the expression
// in isolation from the type arguments.
// falls through
default:
// Anything else treat as an expression.
return false;
}
// Treat anything else as an expression.
return false;
}

function parsePrimaryExpression(): PrimaryExpression {
Expand Down Expand Up @@ -5790,30 +5787,16 @@ namespace ts {
const name = parseIdentifierName();
return finishNode(factory.createMetaProperty(SyntaxKind.NewKeyword, name), pos);
}

const expressionPos = getNodePos();
let expression: MemberExpression = parsePrimaryExpression();
let typeArguments;
while (true) {
expression = parseMemberExpressionRest(expressionPos, expression, /*allowOptionalChain*/ false);
typeArguments = tryParse(parseTypeArgumentsInExpression);
if (isTemplateStartOfTaggedTemplate()) {
Debug.assert(!!typeArguments,
"Expected a type argument list; all plain tagged template starts should be consumed in 'parseMemberExpressionRest'");
expression = parseTaggedTemplateRest(expressionPos, expression, /*optionalChain*/ undefined, typeArguments);
typeArguments = undefined;
}
break;
let expression: LeftHandSideExpression = parseMemberExpressionRest(expressionPos, parsePrimaryExpression(), /*allowOptionalChain*/ false);
let typeArguments: NodeArray<TypeNode> | undefined;
// Absorb type arguments into NewExpression when preceding expression is ExpressionWithTypeArguments
if (expression.kind === SyntaxKind.ExpressionWithTypeArguments) {
typeArguments = (expression as ExpressionWithTypeArguments).typeArguments;
expression = (expression as ExpressionWithTypeArguments).expression;
}

let argumentsArray: NodeArray<Expression> | undefined;
if (token() === SyntaxKind.OpenParenToken) {
argumentsArray = parseArgumentList();
}
else if (typeArguments) {
parseErrorAt(pos, scanner.getStartPos(), Diagnostics.A_new_expression_with_type_arguments_must_always_be_followed_by_a_parenthesized_argument_list);
}
return finishNode(factory.createNewExpression(expression, typeArguments, argumentsArray), pos);
const argumentList = token() === SyntaxKind.OpenParenToken ? parseArgumentList() : undefined;
return finishNode(factory.createNewExpression(expression, typeArguments, argumentList), pos);
}

// STATEMENTS
Expand Down Expand Up @@ -7070,6 +7053,9 @@ namespace ts {
function parseExpressionWithTypeArguments(): ExpressionWithTypeArguments {
const pos = getNodePos();
const expression = parseLeftHandSideExpressionOrHigher();
if (expression.kind === SyntaxKind.ExpressionWithTypeArguments) {
return expression as ExpressionWithTypeArguments;
}
const typeArguments = tryParseTypeArguments();
return finishNode(factory.createExpressionWithTypeArguments(expression, typeArguments), pos);
}
Expand Down
Loading