Skip to content

Named Type Arguments & Partial Type Argument Inference #23696

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

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
648 changes: 482 additions & 166 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

32 changes: 32 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,38 @@
"category": "Error",
"code": 1343
},
"Positional type arguments cannot follow named type arguments.": {
"category": "Error",
"code": 1344
},
"Named type argument conflicts with positional type argument at index '{0}'.": {
"category": "Error",
"code": 1345
},
"Positional type argument conflicts with named type argument '{0}'.": {
"category": "Error",
"code": 1346
},
"Signature has no type argument named '{0}'.": {
"category": "Error",
"code": 1347
},
"No signature has all named type arguments.": {
"category": "Error",
"code": 1348
},
"No signature matches all named and positional type arguments.": {
"category": "Error",
"code": 1349
},
"No signature has a type argument named '{0}'.": {
"category": "Error",
"code": 1350
},
"No value provided for required type argument '{0}'.": {
"category": "Error",
"code": 1351
},

"Duplicate identifier '{0}'.": {
"category": "Error",
Expand Down
18 changes: 14 additions & 4 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,8 @@ namespace ts {
return emitCallSignature(<CallSignatureDeclaration>node);
case SyntaxKind.ConstructSignature:
return emitConstructSignature(<ConstructSignatureDeclaration>node);
case SyntaxKind.NamedTypeArgument:
return emitNamedTypeArgument(<NamedTypeArgument>node);
case SyntaxKind.IndexSignature:
return emitIndexSignature(<IndexSignatureDeclaration>node);

Expand Down Expand Up @@ -1036,7 +1038,7 @@ namespace ts {
function emitIdentifier(node: Identifier) {
const writeText = node.symbol ? writeSymbol : write;
writeText(getTextOfNode(node, /*includeTrivia*/ false), node.symbol);
emitList(node, node.typeArguments, ListFormat.TypeParameters); // Call emitList directly since it could be an array of TypeParameterDeclarations _or_ type arguments
emitTypeParameters(node, node.typeParameters);
}

//
Expand Down Expand Up @@ -1190,6 +1192,14 @@ namespace ts {
popNameGenerationScope(node);
}

function emitNamedTypeArgument(node: NamedTypeArgument) {
emit(node.name);
writeSpace();
writePunctuation("=");
writeSpace();
emit(node.type);
}

function emitIndexSignature(node: IndexSignatureDeclaration) {
emitDecorators(node, node.decorators);
emitModifiers(node, node.modifiers);
Expand Down Expand Up @@ -2760,12 +2770,12 @@ namespace ts {
emitList(parentNode, decorators, ListFormat.Decorators);
}

function emitTypeArguments(parentNode: Node, typeArguments: NodeArray<TypeNode> | undefined) {
function emitTypeArguments(parentNode: Node, typeArguments: NodeArray<TypeArgument> | undefined) {
emitList(parentNode, typeArguments, ListFormat.TypeArguments);
}

function emitTypeParameters(parentNode: SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration | ClassDeclaration | ClassExpression, typeParameters: NodeArray<TypeParameterDeclaration> | undefined) {
if (isFunctionLike(parentNode) && parentNode.typeArguments) { // Quick info uses type arguments in place of type parameters on instantiated signatures
function emitTypeParameters(parentNode: SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration | ClassDeclaration | ClassExpression | Identifier, typeParameters: NodeArray<TypeParameterDeclaration> | undefined) {
if ((isFunctionLike(parentNode) || isIdentifier(parentNode)) && parentNode.typeArguments) { // Quick info uses type arguments in place of type parameters on instantiated signatures
return emitTypeArguments(parentNode, parentNode.typeArguments);
}
emitList(parentNode, typeParameters, ListFormat.TypeParameters);
Expand Down
84 changes: 54 additions & 30 deletions src/compiler/factory.ts

Large diffs are not rendered by default.

34 changes: 29 additions & 5 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ namespace ts {
visitNodes(cbNode, cbNodes, (<SignatureDeclaration>node).typeParameters) ||
visitNodes(cbNode, cbNodes, (<SignatureDeclaration>node).parameters) ||
visitNode(cbNode, (<SignatureDeclaration>node).type);
case SyntaxKind.NamedTypeArgument:
return visitNode(cbNode, (<NamedTypeArgument>node).name) ||
visitNode(cbNode, (<NamedTypeArgument>node).type);
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
case SyntaxKind.Constructor:
Expand Down Expand Up @@ -2251,7 +2254,7 @@ namespace ts {
const node = <TypeReferenceNode>createNode(SyntaxKind.TypeReference);
node.typeName = parseEntityName(/*allowReservedWords*/ true, Diagnostics.Type_expected);
if (!scanner.hasPrecedingLineBreak() && token() === SyntaxKind.LessThanToken) {
node.typeArguments = parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken);
node.typeArguments = parseBracketedList(ParsingContext.TypeArguments, parseNamedTypeArgumentOrType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken);
}
return finishNode(node);
}
Expand Down Expand Up @@ -3124,6 +3127,27 @@ namespace ts {
}
}

function isNamedTypeArgumentStart() {
return token() === SyntaxKind.Identifier && nextToken() === SyntaxKind.EqualsToken;
}

function parseNamedTypeArgument(): NamedTypeArgument {
const node = createNode(SyntaxKind.NamedTypeArgument) as NamedTypeArgument;
node.name = parseIdentifier();
parseExpected(SyntaxKind.EqualsToken);
node.type = parseType();
return finishNode(node);
}

function parseNamedTypeArgumentOrType(): TypeArgument {
if (lookAhead(isNamedTypeArgumentStart)) {
return parseNamedTypeArgument();
}
else {
return parseType();
}
}

function parseType(): TypeNode {
// The rules about 'yield' only apply to actual code/expression contexts. They don't
// apply to 'type' contexts. So we disable these parameters here before moving on.
Expand Down Expand Up @@ -4482,7 +4506,7 @@ namespace ts {
return token() === SyntaxKind.NoSubstitutionTemplateLiteral || token() === SyntaxKind.TemplateHead;
}

function parseTaggedTemplateRest(tag: LeftHandSideExpression, typeArguments: NodeArray<TypeNode> | undefined) {
function parseTaggedTemplateRest(tag: LeftHandSideExpression, typeArguments: NodeArray<TypeArgument> | undefined) {
const tagExpression = <TaggedTemplateExpression>createNode(SyntaxKind.TaggedTemplateExpression, tag.pos);
tagExpression.tag = tag;
tagExpression.typeArguments = typeArguments;
Expand Down Expand Up @@ -4541,7 +4565,7 @@ namespace ts {
return undefined;
}

const typeArguments = parseDelimitedList(ParsingContext.TypeArguments, parseType);
const typeArguments = parseDelimitedList(ParsingContext.TypeArguments, parseNamedTypeArgumentOrType);
if (!parseExpected(SyntaxKind.GreaterThanToken)) {
// If it doesn't have the closing `>` then it's definitely not an type argument list.
return undefined;
Expand Down Expand Up @@ -5874,9 +5898,9 @@ namespace ts {
return finishNode(node);
}

function tryParseTypeArguments(): NodeArray<TypeNode> | undefined {
function tryParseTypeArguments(): NodeArray<TypeArgument> | undefined {
return token() === SyntaxKind.LessThanToken
? parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken)
? parseBracketedList(ParsingContext.TypeArguments, parseNamedTypeArgumentOrType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken)
: undefined;
}

Expand Down
29 changes: 20 additions & 9 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ namespace ts {
SetAccessor,
CallSignature,
ConstructSignature,
NamedTypeArgument,
IndexSignature,
// Type
TypePredicate,
Expand Down Expand Up @@ -731,7 +732,8 @@ namespace ts {
/*@internal*/ autoGenerateFlags?: GeneratedIdentifierFlags; // Specifies whether to auto-generate the text for an identifier.
/*@internal*/ autoGenerateId?: number; // Ensures unique generated identifiers get unique names, but clones get the same name.
isInJSDocNamespace?: boolean; // if the node is a member in a JSDoc namespace
/*@internal*/ typeArguments?: NodeArray<TypeNode | TypeParameterDeclaration>; // Only defined on synthesized nodes. Though not syntactically valid, used in emitting diagnostics, quickinfo, and signature help.
/*@internal*/ typeArguments?: NodeArray<TypeArgument>; // Only defined on synthesized nodes. Though not syntactically valid, used in emitting diagnostics, quickinfo, and signature help.
/*@internal*/ typeParameters?: NodeArray<TypeParameterDeclaration>; // Same as above, but for parameters
/*@internal*/ jsdocDotPos?: number; // Identifier occurs in JSDoc-style generic: Id.<T>
}

Expand Down Expand Up @@ -815,7 +817,7 @@ namespace ts {
typeParameters?: NodeArray<TypeParameterDeclaration>;
parameters: NodeArray<ParameterDeclaration>;
type?: TypeNode;
/* @internal */ typeArguments?: NodeArray<TypeNode>; // Used for quick info, replaces typeParameters for instantiated signatures
/* @internal */ typeArguments?: NodeArray<TypeArgument>; // Used for quick info, replaces typeParameters for instantiated signatures
}

export type SignatureDeclaration =
Expand Down Expand Up @@ -1066,6 +1068,14 @@ namespace ts {
_typeNodeBrand: any;
}

export interface NamedTypeArgument extends Node {
kind: SyntaxKind.NamedTypeArgument;
name: Identifier;
type: TypeNode;
}

export type TypeArgument = TypeNode | NamedTypeArgument;

export interface KeywordTypeNode extends TypeNode {
kind: SyntaxKind.AnyKeyword
| SyntaxKind.NumberKeyword
Expand Down Expand Up @@ -1105,7 +1115,7 @@ namespace ts {
}

export interface NodeWithTypeArguments extends TypeNode {
typeArguments?: NodeArray<TypeNode>;
typeArguments?: NodeArray<TypeArgument>;
}

export type TypeReferenceType = TypeReferenceNode | ExpressionWithTypeArguments;
Expand Down Expand Up @@ -1709,7 +1719,7 @@ namespace ts {
export interface CallExpression extends LeftHandSideExpression, Declaration {
kind: SyntaxKind.CallExpression;
expression: LeftHandSideExpression;
typeArguments?: NodeArray<TypeNode>;
typeArguments?: NodeArray<TypeArgument>;
arguments: NodeArray<Expression>;
}

Expand All @@ -1731,14 +1741,14 @@ namespace ts {
export interface NewExpression extends PrimaryExpression, Declaration {
kind: SyntaxKind.NewExpression;
expression: LeftHandSideExpression;
typeArguments?: NodeArray<TypeNode>;
typeArguments?: NodeArray<TypeArgument>;
arguments?: NodeArray<Expression>;
}

export interface TaggedTemplateExpression extends MemberExpression {
kind: SyntaxKind.TaggedTemplateExpression;
tag: LeftHandSideExpression;
typeArguments?: NodeArray<TypeNode>;
typeArguments?: NodeArray<TypeArgument>;
template: TemplateLiteral;
}

Expand Down Expand Up @@ -1795,15 +1805,15 @@ namespace ts {
kind: SyntaxKind.JsxOpeningElement;
parent: JsxElement;
tagName: JsxTagNameExpression;
typeArguments?: NodeArray<TypeNode>;
typeArguments?: NodeArray<TypeArgument>;
attributes: JsxAttributes;
}

/// A JSX expression of the form <TagName attrs />
export interface JsxSelfClosingElement extends PrimaryExpression {
kind: SyntaxKind.JsxSelfClosingElement;
tagName: JsxTagNameExpression;
typeArguments?: NodeArray<TypeNode>;
typeArguments?: NodeArray<TypeArgument>;
attributes: JsxAttributes;
}

Expand Down Expand Up @@ -2900,7 +2910,7 @@ namespace ts {
typeToTypeNode(type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): TypeNode | undefined;
/* @internal */ typeToTypeNode(type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker): TypeNode | undefined; // tslint:disable-line unified-signatures
/** Note that the resulting nodes cannot be checked. */
signatureToSignatureDeclaration(signature: Signature, kind: SyntaxKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): SignatureDeclaration & {typeArguments?: NodeArray<TypeNode>} | undefined;
signatureToSignatureDeclaration(signature: Signature, kind: SyntaxKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): SignatureDeclaration & {typeArguments?: NodeArray<TypeArgument>} | undefined;
/** Note that the resulting nodes cannot be checked. */
indexInfoToIndexSignatureDeclaration(indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags): IndexSignatureDeclaration | undefined;
/** Note that the resulting nodes cannot be checked. */
Expand Down Expand Up @@ -3538,6 +3548,7 @@ namespace ts {
ContainsStatic = 1 << 9, // Synthetic property with static constituent(s)
Late = 1 << 10, // Late-bound symbol for a computed property with a dynamic name
ReverseMapped = 1 << 11, // property of reverse-inferred homomorphic mapped type.
ImpliedInferDecl = 1 << 12, // Transitent symbol is for an implied `infer` type
Synthetic = SyntheticProperty | SyntheticMethod
}

Expand Down
17 changes: 16 additions & 1 deletion src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -968,7 +968,13 @@ namespace ts {
return (<ArrayTypeNode>node).elementType;
}
else if (node && node.kind === SyntaxKind.TypeReference) {
return singleOrUndefined((<TypeReferenceNode>node).typeArguments);
const argumentNode = singleOrUndefined((<TypeReferenceNode>node).typeArguments);
if (argumentNode && isNamedTypeArgument(argumentNode)) {
return argumentNode.type;
}
else {
return argumentNode;
}
}
else {
return undefined;
Expand Down Expand Up @@ -1505,6 +1511,7 @@ namespace ts {
isIdentifier(node.typeName) &&
node.typeName.escapedText === "Object" &&
node.typeArguments && node.typeArguments.length === 2 &&
node.typeArguments[1].kind !== SyntaxKind.NamedTypeArgument &&
(node.typeArguments[0].kind === SyntaxKind.StringKeyword || node.typeArguments[0].kind === SyntaxKind.NumberKeyword);
}

Expand Down Expand Up @@ -4930,6 +4937,10 @@ namespace ts {
return node.kind === SyntaxKind.ConstructSignature;
}

export function isNamedTypeArgument(node: Node): node is NamedTypeArgument {
return node.kind === SyntaxKind.NamedTypeArgument;
}

export function isIndexSignatureDeclaration(node: Node): node is IndexSignatureDeclaration {
return node.kind === SyntaxKind.IndexSignature;
}
Expand Down Expand Up @@ -5787,6 +5798,10 @@ namespace ts {
return isTypeNodeKind(node.kind);
}

export function isTypeArgument(node: Node): node is TypeArgument {
return isTypeNodeKind(node.kind) || isNamedTypeArgument(node);
}

export function isFunctionOrConstructorTypeNode(node: Node): node is FunctionTypeNode | ConstructorTypeNode {
switch (node.kind) {
case SyntaxKind.FunctionType:
Expand Down
Loading