diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e050c08bf8006..199158bcfa78e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7526,6 +7526,18 @@ namespace ts { return links.resolvedType; } + function getTypeFromTypeCallNode(node: TypeCallTypeNode): Type { + const fn = typeToExpression(node.type); + const args = map(node.arguments, typeToExpression); + const callExpr = createCall(fn, node.typeArguments, args); + return checkExpression(callExpr); + } + + // null! as type + function typeToExpression(type: TypeNode): Expression { + return createAsExpression(createNonNullExpression(createNull()), type); + } + function createIndexedAccessType(objectType: Type, indexType: Type) { const type = createType(TypeFlags.IndexedAccess); type.objectType = objectType; @@ -7985,6 +7997,8 @@ namespace ts { return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); case SyntaxKind.TypeOperator: return getTypeFromTypeOperatorNode(node); + case SyntaxKind.TypeCall: + return getTypeFromTypeCallNode(node); case SyntaxKind.IndexedAccessType: return getTypeFromIndexedAccessTypeNode(node); case SyntaxKind.MappedType: @@ -13161,7 +13175,7 @@ namespace ts { return node.contextualType; } const parent = node.parent; - switch (parent.kind) { + switch (parent && parent.kind) { case SyntaxKind.VariableDeclaration: case SyntaxKind.Parameter: case SyntaxKind.PropertyDeclaration: diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 5444c618353bd..641f4dbc833d3 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -753,6 +753,8 @@ namespace ts { return emitPropertyAccessExpression(node); case SyntaxKind.ElementAccessExpression: return emitElementAccessExpression(node); + case SyntaxKind.TypeCall: + return emitTypeCall(node); case SyntaxKind.CallExpression: return emitCallExpression(node); case SyntaxKind.NewExpression: @@ -1240,6 +1242,12 @@ namespace ts { write("]"); } + function emitTypeCall(node: TypeCallTypeNode) { + emit(node.type); + emitTypeArguments(node, node.typeArguments); + emitList(node, node.arguments, ListFormat.CallExpressionArguments); + } + function emitCallExpression(node: CallExpression) { emitExpression(node.expression); emitTypeArguments(node, node.typeArguments); diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index daec1bce1e8b5..be310b80cedcf 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -888,6 +888,22 @@ namespace ts { : node; } + export function createTypeCall(type: TypeNode, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray) { + const node = createSynthesizedNode(SyntaxKind.TypeCall); + node.type = parenthesizeElementTypeMember(type); + node.typeArguments = asNodeArray(typeArguments); + node.arguments = parenthesizeElementTypeMembers(createNodeArray(argumentsArray)); + return node; + } + + export function updateTypeCall(node: TypeCallTypeNode, type: TypeNode, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray) { + return node.type !== type + || node.typeArguments !== typeArguments + || node.arguments !== argumentsArray + ? updateNode(createTypeCall(type, typeArguments, argumentsArray), node) + : node; + } + export function createCall(expression: Expression, typeArguments: ReadonlyArray | undefined, argumentsArray: ReadonlyArray) { const node = createSynthesizedNode(SyntaxKind.CallExpression); node.expression = parenthesizeForAccess(expression); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index a2fe752ad18e9..261a54045bb75 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -170,6 +170,10 @@ namespace ts { case SyntaxKind.ElementAccessExpression: return visitNode(cbNode, (node).expression) || visitNode(cbNode, (node).argumentExpression); + case SyntaxKind.TypeCall: + return visitNode(cbNode, (node).type) || + visitNodes(cbNode, cbNodes, (node).typeArguments) || + visitNodes(cbNode, cbNodes, (node).arguments); case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: return visitNode(cbNode, (node).expression) || @@ -2738,8 +2742,8 @@ namespace ts { return token() === SyntaxKind.CloseParenToken || isStartOfParameter() || isStartOfType(); } - function parseJSDocPostfixTypeOrHigher(): TypeNode { - const type = parseNonArrayType(); + function parseJSDocPostfixTypeOrHigher(typeNode?: TypeNode): TypeNode { + const type = typeNode || parseNonArrayType(); const kind = getKind(token()); if (!kind) return type; nextToken(); @@ -2761,8 +2765,8 @@ namespace ts { } } - function parseArrayTypeOrHigher(): TypeNode { - let type = parseJSDocPostfixTypeOrHigher(); + function parseArrayTypeOrHigher(typeNode?: TypeNode): TypeNode { + let type = parseJSDocPostfixTypeOrHigher(typeNode); while (!scanner.hasPrecedingLineBreak() && parseOptional(SyntaxKind.OpenBracketToken)) { if (isStartOfType()) { const node = createNode(SyntaxKind.IndexedAccessType, type.pos); @@ -2794,7 +2798,7 @@ namespace ts { case SyntaxKind.KeyOfKeyword: return parseTypeOperator(SyntaxKind.KeyOfKeyword); } - return parseArrayTypeOrHigher(); + return parseTypeCallRest(); } function parseUnionOrIntersectionType(kind: SyntaxKind.UnionType | SyntaxKind.IntersectionType, parseConstituentType: () => TypeNode, operator: SyntaxKind.BarToken | SyntaxKind.AmpersandToken): TypeNode { @@ -4240,6 +4244,46 @@ namespace ts { } } + // type equivalent of parseCallExpressionRest + function parseTypeCallRest(type?: TypeNode): TypeNode { + while (true) { + type = parseArrayTypeOrHigher(type); + if (token() === SyntaxKind.LessThanToken) { + // 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) { + return type; + } + + const callExpr = createNode(SyntaxKind.TypeCall, type.pos); + callExpr.type = type; + callExpr.typeArguments = typeArguments; + callExpr.arguments = parseTypeArgumentList(); + type = finishNode(callExpr); + continue; + } + else if (token() === SyntaxKind.OpenParenToken) { + const callExpr = createNode(SyntaxKind.TypeCall, type.pos); + callExpr.type = type; + callExpr.arguments = parseTypeArgumentList(); + type = finishNode(callExpr); + continue; + } + + return type; + } + } + + function parseTypeArgumentList() { + parseExpected(SyntaxKind.OpenParenToken); + const result = parseDelimitedList(ParsingContext.TypeArguments, parseType); + parseExpected(SyntaxKind.CloseParenToken); + return result; + } + function parseCallExpressionRest(expression: LeftHandSideExpression): LeftHandSideExpression { while (true) { expression = parseMemberExpressionRest(expression); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0a09bb5b6ecba..345d23f2f4a63 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -240,6 +240,7 @@ namespace ts { IndexedAccessType, MappedType, LiteralType, + TypeCall, // Binding patterns ObjectBindingPattern, ArrayBindingPattern, @@ -398,7 +399,7 @@ namespace ts { FirstFutureReservedWord = ImplementsKeyword, LastFutureReservedWord = YieldKeyword, FirstTypeNode = TypePredicate, - LastTypeNode = LiteralType, + LastTypeNode = TypeCall, FirstPunctuation = OpenBraceToken, LastPunctuation = CaretEqualsToken, FirstToken = Unknown, @@ -1495,6 +1496,13 @@ namespace ts { arguments: NodeArray; } + export interface TypeCallTypeNode extends TypeNode { + kind: SyntaxKind.TypeCall; + type: TypeNode; + typeArguments?: NodeArray; + arguments: NodeArray; + } + // see: https://tc39.github.io/ecma262/#prod-SuperCall export interface SuperCall extends CallExpression { expression: SuperExpression; diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 1ce42199372d8..0592b84a50f03 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -446,6 +446,12 @@ namespace ts { visitNode((node).expression, visitor, isExpression), visitNode((node).argumentExpression, visitor, isExpression)); + case SyntaxKind.TypeCall: + return updateTypeCall(node, + visitNode((node).type, visitor, isTypeNode), + nodesVisitor((node).typeArguments, visitor, isTypeNode), + nodesVisitor((node).arguments, visitor, isTypeNode)); + case SyntaxKind.CallExpression: return updateCall(node, visitNode((node).expression, visitor, isExpression), @@ -1391,6 +1397,10 @@ namespace ts { result = reduceNode((node).expression, cbNode, result); break; + case SyntaxKind.TypeCall: + result = reduceNode((node).type, cbNode, result); + break; + // Enum case SyntaxKind.EnumMember: result = reduceNode((node).name, cbNode, result); diff --git a/tests/baselines/reference/typeCall.js b/tests/baselines/reference/typeCall.js new file mode 100644 index 0000000000000..df5c1c0539e9e --- /dev/null +++ b/tests/baselines/reference/typeCall.js @@ -0,0 +1,34 @@ +//// [typeCall.ts] +type F1 = () => 1; +type a = F1(); + +type F2 = (a: string) => 1; +type b = F2('foo'); + +interface F3 { + (): 1; + (a: number): 2; + (a: string): 3; +} +type c = F3(); +type d = F3(123); +type e = F3('foo'); + +declare function f4(a: string): 1; +let a = 'foo'; +type f = typeof f4(typeof a); + +type g = (() => 1)(); + +type Id = (v: T) => T; +type h = Id(123); + +type Wrap = Id(T); +type i = Wrap<123>; + +type F5 = () => () => { a: () => 1; }; +type j = F5()()['a'](); + + +//// [typeCall.js] +var a = 'foo'; diff --git a/tests/baselines/reference/typeCall.symbols b/tests/baselines/reference/typeCall.symbols new file mode 100644 index 0000000000000..4fae98fd51250 --- /dev/null +++ b/tests/baselines/reference/typeCall.symbols @@ -0,0 +1,82 @@ +=== tests/cases/compiler/typeCall.ts === +type F1 = () => 1; +>F1 : Symbol(F1, Decl(typeCall.ts, 0, 0)) + +type a = F1(); +>a : Symbol(a, Decl(typeCall.ts, 0, 18), Decl(typeCall.ts, 16, 3)) +>F1 : Symbol(F1, Decl(typeCall.ts, 0, 0)) + +type F2 = (a: string) => 1; +>F2 : Symbol(F2, Decl(typeCall.ts, 1, 14)) +>a : Symbol(a, Decl(typeCall.ts, 3, 11)) + +type b = F2('foo'); +>b : Symbol(b, Decl(typeCall.ts, 3, 27)) +>F2 : Symbol(F2, Decl(typeCall.ts, 1, 14)) + +interface F3 { +>F3 : Symbol(F3, Decl(typeCall.ts, 4, 19)) + + (): 1; + (a: number): 2; +>a : Symbol(a, Decl(typeCall.ts, 8, 5)) + + (a: string): 3; +>a : Symbol(a, Decl(typeCall.ts, 9, 5)) +} +type c = F3(); +>c : Symbol(c, Decl(typeCall.ts, 10, 1)) +>F3 : Symbol(F3, Decl(typeCall.ts, 4, 19)) + +type d = F3(123); +>d : Symbol(d, Decl(typeCall.ts, 11, 14)) +>F3 : Symbol(F3, Decl(typeCall.ts, 4, 19)) + +type e = F3('foo'); +>e : Symbol(e, Decl(typeCall.ts, 12, 17)) +>F3 : Symbol(F3, Decl(typeCall.ts, 4, 19)) + +declare function f4(a: string): 1; +>f4 : Symbol(f4, Decl(typeCall.ts, 13, 19)) +>a : Symbol(a, Decl(typeCall.ts, 15, 20)) + +let a = 'foo'; +>a : Symbol(a, Decl(typeCall.ts, 0, 18), Decl(typeCall.ts, 16, 3)) + +type f = typeof f4(typeof a); +>f : Symbol(f, Decl(typeCall.ts, 16, 14)) +>f4 : Symbol(f4, Decl(typeCall.ts, 13, 19)) +>a : Symbol(a, Decl(typeCall.ts, 0, 18), Decl(typeCall.ts, 16, 3)) + +type g = (() => 1)(); +>g : Symbol(g, Decl(typeCall.ts, 17, 29)) + +type Id = (v: T) => T; +>Id : Symbol(Id, Decl(typeCall.ts, 19, 21)) +>T : Symbol(T, Decl(typeCall.ts, 21, 11)) +>v : Symbol(v, Decl(typeCall.ts, 21, 14)) +>T : Symbol(T, Decl(typeCall.ts, 21, 11)) +>T : Symbol(T, Decl(typeCall.ts, 21, 11)) + +type h = Id(123); +>h : Symbol(h, Decl(typeCall.ts, 21, 25)) +>Id : Symbol(Id, Decl(typeCall.ts, 19, 21)) + +type Wrap = Id(T); +>Wrap : Symbol(Wrap, Decl(typeCall.ts, 22, 17)) +>T : Symbol(T, Decl(typeCall.ts, 24, 10)) +>Id : Symbol(Id, Decl(typeCall.ts, 19, 21)) +>T : Symbol(T, Decl(typeCall.ts, 24, 10)) + +type i = Wrap<123>; +>i : Symbol(i, Decl(typeCall.ts, 24, 21)) +>Wrap : Symbol(Wrap, Decl(typeCall.ts, 22, 17)) + +type F5 = () => () => { a: () => 1; }; +>F5 : Symbol(F5, Decl(typeCall.ts, 25, 19)) +>a : Symbol(a, Decl(typeCall.ts, 27, 23)) + +type j = F5()()['a'](); +>j : Symbol(j, Decl(typeCall.ts, 27, 38)) +>F5 : Symbol(F5, Decl(typeCall.ts, 25, 19)) + diff --git a/tests/baselines/reference/typeCall.types b/tests/baselines/reference/typeCall.types new file mode 100644 index 0000000000000..a0284f8222d37 --- /dev/null +++ b/tests/baselines/reference/typeCall.types @@ -0,0 +1,83 @@ +=== tests/cases/compiler/typeCall.ts === +type F1 = () => 1; +>F1 : F1 + +type a = F1(); +>a : 1 +>F1 : F1 + +type F2 = (a: string) => 1; +>F2 : F2 +>a : string + +type b = F2('foo'); +>b : 1 +>F2 : F2 + +interface F3 { +>F3 : F3 + + (): 1; + (a: number): 2; +>a : number + + (a: string): 3; +>a : string +} +type c = F3(); +>c : 1 +>F3 : F3 + +type d = F3(123); +>d : 2 +>F3 : F3 + +type e = F3('foo'); +>e : 3 +>F3 : F3 + +declare function f4(a: string): 1; +>f4 : (a: string) => 1 +>a : string + +let a = 'foo'; +>a : string +>'foo' : "foo" + +type f = typeof f4(typeof a); +>f : 1 +>f4 : (a: string) => 1 +>a : string + +type g = (() => 1)(); +>g : 1 + +type Id = (v: T) => T; +>Id : Id +>T : T +>v : T +>T : T +>T : T + +type h = Id(123); +>h : 123 +>Id : Id + +type Wrap = Id(T); +>Wrap : T +>T : T +>Id : Id +>T : T + +type i = Wrap<123>; +>i : 123 +>Wrap : T + +type F5 = () => () => { a: () => 1; }; +>F5 : F5 +>a : () => 1 + +type j = F5()()['a'](); +>j : 1 +>F5 : F5 + diff --git a/tests/cases/compiler/typeCall.ts b/tests/cases/compiler/typeCall.ts new file mode 100644 index 0000000000000..71853877318c9 --- /dev/null +++ b/tests/cases/compiler/typeCall.ts @@ -0,0 +1,31 @@ +// @allowSyntheticDefaultImports: true + +type F1 = () => 1; +type a = F1(); + +type F2 = (a: string) => 1; +type b = F2('foo'); + +interface F3 { + (): 1; + (a: number): 2; + (a: string): 3; +} +type c = F3(); +type d = F3(123); +type e = F3('foo'); + +declare function f4(a: string): 1; +let a = 'foo'; +type f = typeof f4(typeof a); + +type g = (() => 1)(); + +type Id = (v: T) => T; +type h = Id(123); + +type Wrap = Id(T); +type i = Wrap<123>; + +type F5 = () => () => { a: () => 1; }; +type j = F5()()['a']();