Skip to content

Commit 248c400

Browse files
committed
Add support for "specialize" tag
1 parent 0ed1ee5 commit 248c400

16 files changed

+562
-31
lines changed

src/compiler/binder.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ import {
231231
JSDocParameterTag,
232232
JSDocPropertyLikeTag,
233233
JSDocSignature,
234+
JSDocSpecializeTag,
234235
JSDocTypedefTag,
235236
JSDocTypeLiteral,
236237
JsxAttribute,
@@ -3091,6 +3092,8 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
30913092
return bind((node as JSDocOverloadTag).typeExpression);
30923093
case SyntaxKind.JSDocImportTag:
30933094
return (jsDocImports || (jsDocImports = [])).push(node as JSDocImportTag);
3095+
case SyntaxKind.JSDocSpecializeTag:
3096+
return bindEach((node as JSDocSpecializeTag).typeArguments);
30943097
}
30953098
}
30963099

src/compiler/checker.ts

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ import {
326326
getJSDocParameterTags,
327327
getJSDocRoot,
328328
getJSDocSatisfiesExpressionType,
329+
getJSDocSpecializeTag,
329330
getJSDocTags,
330331
getJSDocThisTag,
331332
getJSDocType,
@@ -35562,15 +35563,34 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3556235563
return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, getNodeLinks(node).resolvedSymbol, indexedAccessType, indexExpression, checkMode), node);
3556335564
}
3556435565

35565-
function callLikeExpressionMayHaveTypeArguments(node: CallLikeExpression): node is CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement {
35566+
function callLikeExpressionMayHaveTypeArguments(node: CallLikeExpression): node is CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningLikeElement {
3556635567
return isCallOrNewExpression(node) || isTaggedTemplateExpression(node) || isJsxOpeningLikeElement(node);
3556735568
}
3556835569

35570+
function getTypeArgumentsForCallLikeExpression(node: CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningLikeElement) {
35571+
if (isSuperCall(node)) {
35572+
return undefined;
35573+
}
35574+
if (isInJSFile(node)) {
35575+
let { parent } = node;
35576+
if (isJsxElement(parent)) {
35577+
parent = parent.parent;
35578+
}
35579+
if (canHaveJSDoc(parent)) {
35580+
const specializeTag = getJSDocSpecializeTag(parent);
35581+
if (specializeTag) {
35582+
return specializeTag.typeArguments;
35583+
}
35584+
}
35585+
}
35586+
return node.typeArguments;
35587+
}
35588+
3556935589
function resolveUntypedCall(node: CallLikeExpression): Signature {
3557035590
if (callLikeExpressionMayHaveTypeArguments(node)) {
3557135591
// Check type arguments even though we will give an error that untyped calls may not accept type arguments.
3557235592
// This gets us diagnostics for the type arguments and marks them as referenced.
35573-
forEach(node.typeArguments, checkSourceElement);
35593+
forEach(getTypeArgumentsForCallLikeExpression(node), checkSourceElement);
3557435594
}
3557535595

3557635596
if (node.kind === SyntaxKind.TaggedTemplateExpression) {
@@ -36570,13 +36590,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3657036590

3657136591
let candidates: Signature[] = [];
3657236592
let typeArguments: NodeArray<TypeNode> | undefined;
36573-
if (!isDecorator && !isInstanceof && !isSuperCall(node) && !isJsxOpenFragment) {
36574-
typeArguments = (node as CallExpression).typeArguments;
36575-
36576-
// We already perform checking on the type arguments on the class declaration itself.
36577-
if (isTaggedTemplate || isJsxOpeningOrSelfClosingElement || (node as CallExpression).expression.kind !== SyntaxKind.SuperKeyword) {
36578-
forEach(typeArguments, checkSourceElement);
36579-
}
36593+
if (callLikeExpressionMayHaveTypeArguments(node)) {
36594+
typeArguments = getTypeArgumentsForCallLikeExpression(node);
36595+
forEach(typeArguments, checkSourceElement);
3658036596
}
3658136597

3658236598
candidates = candidatesOutArray || [];
@@ -36748,7 +36764,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3674836764
diagnostics.add(getArgumentArityError(node, [candidateForArgumentArityError], args, headMessage));
3674936765
}
3675036766
else if (candidateForTypeArgumentError) {
36751-
checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression | TaggedTemplateExpression | JsxOpeningLikeElement).typeArguments!, /*reportErrors*/ true, headMessage);
36767+
checkTypeArguments(candidateForTypeArgumentError, typeArguments!, /*reportErrors*/ true, headMessage);
3675236768
}
3675336769
else if (!isJsxOpenFragment) {
3675436770
const signaturesWithCorrectTypeArgumentArity = filter(signatures, s => hasCorrectTypeArgumentArity(s, typeArguments));
@@ -36953,7 +36969,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3695336969
return candidate;
3695436970
}
3695536971

36956-
const typeArgumentNodes: readonly TypeNode[] | undefined = callLikeExpressionMayHaveTypeArguments(node) ? node.typeArguments : undefined;
36972+
const typeArgumentNodes: readonly TypeNode[] | undefined = callLikeExpressionMayHaveTypeArguments(node) ? getTypeArgumentsForCallLikeExpression(node) : undefined;
3695736973
const instantiated = typeArgumentNodes
3695836974
? createSignatureInstantiation(candidate, getTypeArgumentsFromNodes(typeArgumentNodes, typeParameters, isInJSFile(node)))
3695936975
: inferSignatureInstantiationForOverloadFailure(node, typeParameters, candidate, args, checkMode);
@@ -37053,14 +37069,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3705337069
// that the user will not add any.
3705437070
const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call);
3705537071
const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length;
37072+
const typeArguments = getTypeArgumentsForCallLikeExpression(node);
3705637073

3705737074
// TS 1.0 Spec: 4.12
3705837075
// In an untyped function call no TypeArgs are permitted, Args can be any argument list, no contextual
3705937076
// types are provided for the argument expressions, and the result is always of type Any.
3706037077
if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) {
3706137078
// The unknownType indicates that an error already occurred (and was reported). No
3706237079
// need to report another error in this case.
37063-
if (!isErrorType(funcType) && node.typeArguments) {
37080+
if (!isErrorType(funcType) && typeArguments) {
3706437081
error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments);
3706537082
}
3706637083
return resolveUntypedCall(node);
@@ -37096,7 +37113,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3709637113
// use the resolvingSignature singleton to indicate that we deferred processing. This result will be
3709737114
// propagated out and eventually turned into silentNeverType (a type that is assignable to anything and
3709837115
// from which we never make inferences).
37099-
if (checkMode & CheckMode.SkipGenericFunctions && !node.typeArguments && callSignatures.some(isGenericFunctionReturningFunction)) {
37116+
if (checkMode & CheckMode.SkipGenericFunctions && !typeArguments && callSignatures.some(isGenericFunctionReturningFunction)) {
3710037117
skippedGenericFunction(node, checkMode);
3710137118
return resolvingSignature;
3710237119
}
@@ -37141,11 +37158,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3714137158
return resolveErrorCall(node);
3714237159
}
3714337160

37161+
const typeArguments = getTypeArgumentsForCallLikeExpression(node);
3714437162
// TS 1.0 spec: 4.11
3714537163
// If expressionType is of type Any, Args can be any argument
3714637164
// list and the result of the operation is of type Any.
3714737165
if (isTypeAny(expressionType)) {
37148-
if (node.typeArguments) {
37166+
if (typeArguments) {
3714937167
error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments);
3715037168
}
3715137169
return resolveUntypedCall(node);
@@ -37545,9 +37563,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3754537563
const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node);
3754637564
const fakeSignature = createSignatureForJSXIntrinsic(node, result);
3754737565
checkTypeAssignableToAndOptionallyElaborate(checkExpressionWithContextualType(node.attributes, getEffectiveFirstArgumentForJsxSignature(fakeSignature, node), /*inferenceContext*/ undefined, CheckMode.Normal), result, node.tagName, node.attributes);
37548-
if (length(node.typeArguments)) {
37549-
forEach(node.typeArguments, checkSourceElement);
37550-
diagnostics.add(createDiagnosticForNodeArray(getSourceFileOfNode(node), node.typeArguments!, Diagnostics.Expected_0_type_arguments_but_got_1, 0, length(node.typeArguments)));
37566+
const typeArguments = getTypeArgumentsForCallLikeExpression(node);
37567+
if (length(typeArguments)) {
37568+
forEach(typeArguments, checkSourceElement);
37569+
diagnostics.add(createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments!, Diagnostics.Expected_0_type_arguments_but_got_1, 0, length(typeArguments)));
3755137570
}
3755237571
return fakeSignature;
3755337572
}
@@ -37804,7 +37823,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3780437823
* @returns On success, the expression's signature's return type. On failure, anyType.
3780537824
*/
3780637825
function checkCallExpression(node: CallExpression | NewExpression, checkMode?: CheckMode): Type {
37807-
checkGrammarTypeArguments(node, node.typeArguments);
37826+
checkGrammarTypeArguments(node, getTypeArgumentsForCallLikeExpression(node));
3780837827

3780937828
const signature = getResolvedSignature(node, /*candidatesOutArray*/ undefined, checkMode);
3781037829
if (signature === resolvingSignature) {
@@ -38043,7 +38062,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3804338062
}
3804438063

3804538064
function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type {
38046-
if (!checkGrammarTaggedTemplateChain(node)) checkGrammarTypeArguments(node, node.typeArguments);
38065+
if (!checkGrammarTaggedTemplateChain(node)) checkGrammarTypeArguments(node, getTypeArgumentsForCallLikeExpression(node));
3804738066
if (languageVersion < LanguageFeatureMinimumTarget.TaggedTemplates) {
3804838067
checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject);
3804938068
}
@@ -42661,7 +42680,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4266142680
}
4266242681

4266342682
function getEffectiveTypeArguments(node: TypeReferenceNode | ExpressionWithTypeArguments | NodeWithTypeArguments, typeParameters: readonly TypeParameter[]): Type[] {
42664-
return fillMissingTypeArguments(map(node.typeArguments!, getTypeFromTypeNode), typeParameters, getMinTypeArgumentCount(typeParameters), isInJSFile(node));
42683+
return fillMissingTypeArguments(map(node.typeArguments || [], getTypeFromTypeNode), typeParameters, getMinTypeArgumentCount(typeParameters), isInJSFile(node));
4266542684
}
4266642685

4266742686
function checkTypeArgumentConstraints(node: TypeReferenceNode | ExpressionWithTypeArguments | NodeWithTypeArguments, typeParameters: readonly TypeParameter[]): boolean {
@@ -52696,7 +52715,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
5269652715

5269752716
function checkGrammarJsxElement(node: JsxOpeningLikeElement) {
5269852717
checkGrammarJsxName(node.tagName);
52699-
checkGrammarTypeArguments(node, node.typeArguments);
52718+
checkGrammarTypeArguments(node, getTypeArgumentsForCallLikeExpression(node));
5270052719
const seen = new Map<__String, boolean>();
5270152720

5270252721
for (const attr of node.attributes.properties) {

src/compiler/emitter.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ import {
257257
JSDocSatisfiesTag,
258258
JSDocSeeTag,
259259
JSDocSignature,
260+
JSDocSpecializeTag,
260261
JSDocTag,
261262
JSDocTemplateTag,
262263
JSDocThisTag,
@@ -1899,6 +1900,8 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
18991900
return emitJSDocSeeTag(node as JSDocSeeTag);
19001901
case SyntaxKind.JSDocImportTag:
19011902
return emitJSDocImportTag(node as JSDocImportTag);
1903+
case SyntaxKind.JSDocSpecializeTag:
1904+
return emitJSDocSpecializeTag(node as JSDocSpecializeTag);
19021905
// SyntaxKind.JSDocPropertyTag (see JSDocParameterTag, above)
19031906

19041907
// Transformation nodes
@@ -4156,6 +4159,15 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
41564159
emitJSDocComment(tag.comment);
41574160
}
41584161

4162+
function emitJSDocSpecializeTag(tag: JSDocSpecializeTag) {
4163+
emitJSDocTagName(tag.tagName);
4164+
writeSpace();
4165+
writePunctuation("<");
4166+
emitList(tag, tag.typeArguments, ListFormat.CommaListElements);
4167+
writePunctuation(">");
4168+
emitJSDocComment(tag.comment);
4169+
}
4170+
41594171
function emitJSDocNameReference(node: JSDocNameReference) {
41604172
writeSpace();
41614173
writePunctuation("{");

src/compiler/factory/nodeFactory.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ import {
251251
JSDocSatisfiesTag,
252252
JSDocSeeTag,
253253
JSDocSignature,
254+
JSDocSpecializeTag,
254255
JSDocTag,
255256
JSDocTemplateTag,
256257
JSDocText,
@@ -863,6 +864,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
863864
updateJSDocSeeTag,
864865
createJSDocImportTag,
865866
updateJSDocImportTag,
867+
createJSDocSpecializeTag,
868+
updateJSDocSpecializeTag,
866869
createJSDocNameReference,
867870
updateJSDocNameReference,
868871
createJSDocMemberName,
@@ -5564,6 +5567,22 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
55645567
: node;
55655568
}
55665569

5570+
// @api
5571+
function createJSDocSpecializeTag(tagName: Identifier | undefined, typeArguments: readonly TypeNode[], comment: string | NodeArray<JSDocComment> | undefined): JSDocSpecializeTag {
5572+
const node = createBaseJSDocTag<JSDocSpecializeTag>(SyntaxKind.JSDocSpecializeTag, tagName ?? createIdentifier("specialize"), /*comment*/ undefined);
5573+
node.typeArguments = asNodeArray(typeArguments);
5574+
node.comment = comment;
5575+
return node;
5576+
}
5577+
5578+
function updateJSDocSpecializeTag(node: JSDocSpecializeTag, tagName: Identifier | undefined, typeArguments: readonly TypeNode[], comment: string | NodeArray<JSDocComment> | undefined): JSDocSpecializeTag {
5579+
return node.tagName !== tagName
5580+
|| node.typeArguments !== typeArguments
5581+
|| node.comment !== comment
5582+
? update(createJSDocSpecializeTag(tagName, typeArguments, comment), node)
5583+
: node;
5584+
}
5585+
55675586
// @api
55685587
function createJSDocText(text: string): JSDocText {
55695588
const node = createBaseNode<JSDocText>(SyntaxKind.JSDocText);
@@ -7227,6 +7246,8 @@ function getDefaultTagNameForKind(kind: JSDocTag["kind"]): string {
72277246
return "implements";
72287247
case SyntaxKind.JSDocImportTag:
72297248
return "import";
7249+
case SyntaxKind.JSDocSpecializeTag:
7250+
return "specialize";
72307251
default:
72317252
return Debug.fail(`Unsupported kind: ${Debug.formatSyntaxKind(kind)}`);
72327253
}

src/compiler/factory/nodeTests.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ import {
113113
JSDocSatisfiesTag,
114114
JSDocSeeTag,
115115
JSDocSignature,
116+
JSDocSpecializeTag,
116117
JSDocTemplateTag,
117118
JSDocThisTag,
118119
JSDocThrowsTag,
@@ -1193,6 +1194,10 @@ export function isJSDocImportTag(node: Node): node is JSDocImportTag {
11931194
return node.kind === SyntaxKind.JSDocImportTag;
11941195
}
11951196

1197+
export function isJSDocSpecializeTag(node: Node): node is JSDocSpecializeTag {
1198+
return node.kind === SyntaxKind.JSDocSpecializeTag;
1199+
}
1200+
11961201
// Synthesized list
11971202

11981203
/** @internal */

src/compiler/parser.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ import {
149149
isJSDocFunctionType,
150150
isJSDocNullableType,
151151
isJSDocReturnTag,
152+
isJSDocSpecializeTag,
152153
isJSDocTypeTag,
153154
isJsxNamespacedName,
154155
isJsxOpeningElement,
@@ -202,6 +203,7 @@ import {
202203
JSDocSatisfiesTag,
203204
JSDocSeeTag,
204205
JSDocSignature,
206+
JSDocSpecializeTag,
205207
JSDocSyntaxKind,
206208
JSDocTag,
207209
JSDocTemplateTag,
@@ -1132,6 +1134,9 @@ const forEachChildTable: ForEachChildTable = {
11321134
[SyntaxKind.JSDocDeprecatedTag]: forEachChildInJSDocTag,
11331135
[SyntaxKind.JSDocOverrideTag]: forEachChildInJSDocTag,
11341136
[SyntaxKind.JSDocImportTag]: forEachChildInJSDocImportTag,
1137+
[SyntaxKind.JSDocSpecializeTag]: function forEachChildInJSDocSpecializeTag<T>(node: JSDocSpecializeTag, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
1138+
return forEach(node.typeArguments, cbNode);
1139+
},
11351140
[SyntaxKind.PartiallyEmittedExpression]: forEachChildInPartiallyEmittedExpression,
11361141
};
11371142

@@ -9177,6 +9182,9 @@ namespace Parser {
91779182
case "import":
91789183
tag = parseImportTag(start, tagName, margin, indentText);
91799184
break;
9185+
case "specialize":
9186+
tag = parseSpecializeTag(start, tagName, margin, indentText);
9187+
break;
91809188
default:
91819189
tag = parseUnknownTag(start, tagName, margin, indentText);
91829190
break;
@@ -9565,6 +9573,24 @@ namespace Parser {
95659573
return finishNode(factory.createJSDocImportTag(tagName, importClause, moduleSpecifier, attributes, comments), start);
95669574
}
95679575

9576+
function parseSpecializeTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocSpecializeTag {
9577+
if (some(tags, isJSDocSpecializeTag)) {
9578+
parseErrorAt(tagName.pos, scanner.getTokenStart(), Diagnostics._0_tag_already_specified, unescapeLeadingUnderscores(tagName.escapedText));
9579+
}
9580+
const pos = getNodePos();
9581+
scanner.setSkipJsDocLeadingAsterisks(true);
9582+
let typeArguments: NodeArray<TypeNode>;
9583+
if (token() === SyntaxKind.LessThanToken) {
9584+
typeArguments = parseBracketedList(ParsingContext.TypeArguments, parseJSDocType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken);
9585+
}
9586+
else {
9587+
typeArguments = parseBracketedList(ParsingContext.TypeArguments, parseJSDocType, SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken);
9588+
}
9589+
scanner.setSkipJsDocLeadingAsterisks(false);
9590+
const comments = margin !== undefined && indentText !== undefined ? parseTrailingTagComments(start, getNodePos(), margin, indentText) : undefined;
9591+
return finishNode(factory.createJSDocSpecializeTag(tagName, typeArguments, comments), pos);
9592+
}
9593+
95689594
function parseExpressionWithTypeArgumentsForAugments(): ExpressionWithTypeArguments & { expression: Identifier | PropertyAccessEntityNameExpression; } {
95699595
const usedBrace = parseOptional(SyntaxKind.OpenBraceToken);
95709596
const pos = getNodePos();

0 commit comments

Comments
 (0)