Skip to content

Commit 80c2171

Browse files
committed
Add support for "specialize" tag
1 parent d06fb82 commit 80c2171

34 files changed

+1909
-34
lines changed

src/compiler/binder.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ import {
237237
JSDocParameterTag,
238238
JSDocPropertyLikeTag,
239239
JSDocSignature,
240+
JSDocSpecializeTag,
240241
JSDocTypedefTag,
241242
JSDocTypeLiteral,
242243
JsxAttribute,
@@ -3078,6 +3079,8 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
30783079
return bind((node as JSDocOverloadTag).typeExpression);
30793080
case SyntaxKind.JSDocImportTag:
30803081
return (jsDocImports || (jsDocImports = [])).push(node as JSDocImportTag);
3082+
case SyntaxKind.JSDocSpecializeTag:
3083+
return bindEach((node as JSDocSpecializeTag).typeArguments);
30813084
}
30823085
}
30833086

src/compiler/checker.ts

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ import {
320320
getJSDocParameterTags,
321321
getJSDocRoot,
322322
getJSDocSatisfiesExpressionType,
323+
getJSDocSpecializeTag,
323324
getJSDocTags,
324325
getJSDocThisTag,
325326
getJSDocType,
@@ -828,7 +829,6 @@ import {
828829
JsxFlags,
829830
JsxFragment,
830831
JsxNamespacedName,
831-
JsxOpeningElement,
832832
JsxOpeningFragment,
833833
JsxOpeningLikeElement,
834834
JsxReferenceKind,
@@ -34743,15 +34743,34 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3474334743
return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, getNodeLinks(node).resolvedSymbol, indexedAccessType, indexExpression, checkMode), node);
3474434744
}
3474534745

34746-
function callLikeExpressionMayHaveTypeArguments(node: CallLikeExpression): node is CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement {
34746+
function callLikeExpressionMayHaveTypeArguments(node: CallLikeExpression): node is CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningLikeElement {
3474734747
return isCallOrNewExpression(node) || isTaggedTemplateExpression(node) || isJsxOpeningLikeElement(node);
3474834748
}
3474934749

34750+
function getTypeArgumentsForCallLikeExpression(node: CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningLikeElement) {
34751+
if (isSuperCall(node)) {
34752+
return undefined;
34753+
}
34754+
if (isInJSFile(node)) {
34755+
let { parent } = node;
34756+
if (isJsxElement(parent)) {
34757+
parent = parent.parent;
34758+
}
34759+
if (canHaveJSDoc(parent)) {
34760+
const specializeTag = getJSDocSpecializeTag(parent);
34761+
if (specializeTag) {
34762+
return specializeTag.typeArguments;
34763+
}
34764+
}
34765+
}
34766+
return node.typeArguments;
34767+
}
34768+
3475034769
function resolveUntypedCall(node: CallLikeExpression): Signature {
3475134770
if (callLikeExpressionMayHaveTypeArguments(node)) {
3475234771
// Check type arguments even though we will give an error that untyped calls may not accept type arguments.
3475334772
// This gets us diagnostics for the type arguments and marks them as referenced.
34754-
forEach(node.typeArguments, checkSourceElement);
34773+
forEach(getTypeArgumentsForCallLikeExpression(node), checkSourceElement);
3475534774
}
3475634775

3475734776
if (node.kind === SyntaxKind.TaggedTemplateExpression) {
@@ -35702,21 +35721,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3570235721
}
3570335722

3570435723
function resolveCall(node: CallLikeExpression, signatures: readonly Signature[], candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, callChainFlags: SignatureFlags, headMessage?: DiagnosticMessage): Signature {
35705-
const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression;
3570635724
const isDecorator = node.kind === SyntaxKind.Decorator;
35707-
const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node);
3570835725
const isInstanceof = node.kind === SyntaxKind.BinaryExpression;
3570935726
const reportErrors = !isInferencePartiallyBlocked && !candidatesOutArray;
3571035727

3571135728
let typeArguments: NodeArray<TypeNode> | undefined;
3571235729

35713-
if (!isDecorator && !isInstanceof && !isSuperCall(node)) {
35714-
typeArguments = (node as CallExpression).typeArguments;
35715-
35716-
// We already perform checking on the type arguments on the class declaration itself.
35717-
if (isTaggedTemplate || isJsxOpeningOrSelfClosingElement || (node as CallExpression).expression.kind !== SyntaxKind.SuperKeyword) {
35718-
forEach(typeArguments, checkSourceElement);
35719-
}
35730+
if (callLikeExpressionMayHaveTypeArguments(node)) {
35731+
typeArguments = getTypeArgumentsForCallLikeExpression(node);
35732+
forEach(typeArguments, checkSourceElement);
3572035733
}
3572135734

3572235735
const candidates = candidatesOutArray || [];
@@ -35888,7 +35901,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3588835901
diagnostics.add(getArgumentArityError(node, [candidateForArgumentArityError], args, headMessage));
3588935902
}
3589035903
else if (candidateForTypeArgumentError) {
35891-
checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression | TaggedTemplateExpression | JsxOpeningLikeElement).typeArguments!, /*reportErrors*/ true, headMessage);
35904+
checkTypeArguments(candidateForTypeArgumentError, typeArguments!, /*reportErrors*/ true, headMessage);
3589235905
}
3589335906
else {
3589435907
const signaturesWithCorrectTypeArgumentArity = filter(signatures, s => hasCorrectTypeArgumentArity(s, typeArguments));
@@ -36102,7 +36115,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3610236115
return candidate;
3610336116
}
3610436117

36105-
const typeArgumentNodes: readonly TypeNode[] | undefined = callLikeExpressionMayHaveTypeArguments(node) ? node.typeArguments : undefined;
36118+
const typeArgumentNodes: readonly TypeNode[] | undefined = callLikeExpressionMayHaveTypeArguments(node) ? getTypeArgumentsForCallLikeExpression(node) : undefined;
3610636119
const instantiated = typeArgumentNodes
3610736120
? createSignatureInstantiation(candidate, getTypeArgumentsFromNodes(typeArgumentNodes, typeParameters, isInJSFile(node)))
3610836121
: inferSignatureInstantiationForOverloadFailure(node, typeParameters, candidate, args, checkMode);
@@ -36202,14 +36215,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3620236215
// that the user will not add any.
3620336216
const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call);
3620436217
const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length;
36218+
const typeArguments = getTypeArgumentsForCallLikeExpression(node);
3620536219

3620636220
// TS 1.0 Spec: 4.12
3620736221
// In an untyped function call no TypeArgs are permitted, Args can be any argument list, no contextual
3620836222
// types are provided for the argument expressions, and the result is always of type Any.
3620936223
if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) {
3621036224
// The unknownType indicates that an error already occurred (and was reported). No
3621136225
// need to report another error in this case.
36212-
if (!isErrorType(funcType) && node.typeArguments) {
36226+
if (!isErrorType(funcType) && typeArguments) {
3621336227
error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments);
3621436228
}
3621536229
return resolveUntypedCall(node);
@@ -36245,7 +36259,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3624536259
// use the resolvingSignature singleton to indicate that we deferred processing. This result will be
3624636260
// propagated out and eventually turned into silentNeverType (a type that is assignable to anything and
3624736261
// from which we never make inferences).
36248-
if (checkMode & CheckMode.SkipGenericFunctions && !node.typeArguments && callSignatures.some(isGenericFunctionReturningFunction)) {
36262+
if (checkMode & CheckMode.SkipGenericFunctions && !typeArguments && callSignatures.some(isGenericFunctionReturningFunction)) {
3624936263
skippedGenericFunction(node, checkMode);
3625036264
return resolvingSignature;
3625136265
}
@@ -36290,11 +36304,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3629036304
return resolveErrorCall(node);
3629136305
}
3629236306

36307+
const typeArguments = getTypeArgumentsForCallLikeExpression(node);
3629336308
// TS 1.0 spec: 4.11
3629436309
// If expressionType is of type Any, Args can be any argument
3629536310
// list and the result of the operation is of type Any.
3629636311
if (isTypeAny(expressionType)) {
36297-
if (node.typeArguments) {
36312+
if (typeArguments) {
3629836313
error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments);
3629936314
}
3630036315
return resolveUntypedCall(node);
@@ -36659,9 +36674,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3665936674
const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node);
3666036675
const fakeSignature = createSignatureForJSXIntrinsic(node, result);
3666136676
checkTypeAssignableToAndOptionallyElaborate(checkExpressionWithContextualType(node.attributes, getEffectiveFirstArgumentForJsxSignature(fakeSignature, node), /*inferenceContext*/ undefined, CheckMode.Normal), result, node.tagName, node.attributes);
36662-
if (length(node.typeArguments)) {
36663-
forEach(node.typeArguments, checkSourceElement);
36664-
diagnostics.add(createDiagnosticForNodeArray(getSourceFileOfNode(node), node.typeArguments!, Diagnostics.Expected_0_type_arguments_but_got_1, 0, length(node.typeArguments)));
36677+
const typeArguments = getTypeArgumentsForCallLikeExpression(node);
36678+
if (length(typeArguments)) {
36679+
forEach(typeArguments, checkSourceElement);
36680+
diagnostics.add(createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments!, Diagnostics.Expected_0_type_arguments_but_got_1, 0, length(typeArguments)));
3666536681
}
3666636682
return fakeSignature;
3666736683
}
@@ -36917,7 +36933,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3691736933
* @returns On success, the expression's signature's return type. On failure, anyType.
3691836934
*/
3691936935
function checkCallExpression(node: CallExpression | NewExpression, checkMode?: CheckMode): Type {
36920-
checkGrammarTypeArguments(node, node.typeArguments);
36936+
checkGrammarTypeArguments(node, getTypeArgumentsForCallLikeExpression(node));
3692136937

3692236938
const signature = getResolvedSignature(node, /*candidatesOutArray*/ undefined, checkMode);
3692336939
if (signature === resolvingSignature) {
@@ -37156,7 +37172,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3715637172
}
3715737173

3715837174
function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type {
37159-
if (!checkGrammarTaggedTemplateChain(node)) checkGrammarTypeArguments(node, node.typeArguments);
37175+
if (!checkGrammarTaggedTemplateChain(node)) {
37176+
checkGrammarTypeArguments(node, getTypeArgumentsForCallLikeExpression(node));
37177+
}
3716037178
if (languageVersion < LanguageFeatureMinimumTarget.TaggedTemplates) {
3716137179
checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject);
3716237180
}
@@ -41719,15 +41737,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4171941737
checkDecorators(node);
4172041738
}
4172141739

41722-
function getEffectiveTypeArgumentAtIndex(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[], index: number): Type {
41740+
function getEffectiveTypeArgumentAtIndex(node: TypeReferenceNode, typeParameters: readonly TypeParameter[], index: number): Type {
4172341741
if (node.typeArguments && index < node.typeArguments.length) {
4172441742
return getTypeFromTypeNode(node.typeArguments[index]);
4172541743
}
4172641744
return getEffectiveTypeArguments(node, typeParameters)[index];
4172741745
}
4172841746

4172941747
function getEffectiveTypeArguments(node: TypeReferenceNode | ExpressionWithTypeArguments | NodeWithTypeArguments, typeParameters: readonly TypeParameter[]): Type[] {
41730-
return fillMissingTypeArguments(map(node.typeArguments!, getTypeFromTypeNode), typeParameters, getMinTypeArgumentCount(typeParameters), isInJSFile(node));
41748+
return fillMissingTypeArguments(map(node.typeArguments || [], getTypeFromTypeNode), typeParameters, getMinTypeArgumentCount(typeParameters), isInJSFile(node));
4173141749
}
4173241750

4173341751
function checkTypeArgumentConstraints(node: TypeReferenceNode | ExpressionWithTypeArguments | NodeWithTypeArguments, typeParameters: readonly TypeParameter[]): boolean {
@@ -51499,7 +51517,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
5149951517

5150051518
function checkGrammarJsxElement(node: JsxOpeningLikeElement) {
5150151519
checkGrammarJsxName(node.tagName);
51502-
checkGrammarTypeArguments(node, node.typeArguments);
51520+
checkGrammarTypeArguments(node, getTypeArgumentsForCallLikeExpression(node));
5150351521
const seen = new Map<__String, boolean>();
5150451522

5150551523
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,
@@ -1876,6 +1877,8 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
18761877
return emitJSDocSeeTag(node as JSDocSeeTag);
18771878
case SyntaxKind.JSDocImportTag:
18781879
return emitJSDocImportTag(node as JSDocImportTag);
1880+
case SyntaxKind.JSDocSpecializeTag:
1881+
return emitJSDocSpecializeTag(node as JSDocSpecializeTag);
18791882
// SyntaxKind.JSDocPropertyTag (see JSDocParameterTag, above)
18801883

18811884
// Transformation nodes
@@ -4059,6 +4062,15 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
40594062
emitJSDocComment(tag.comment);
40604063
}
40614064

4065+
function emitJSDocSpecializeTag(tag: JSDocSpecializeTag) {
4066+
emitJSDocTagName(tag.tagName);
4067+
writeSpace();
4068+
writePunctuation("<");
4069+
emitList(tag, tag.typeArguments, ListFormat.CommaListElements);
4070+
writePunctuation(">");
4071+
emitJSDocComment(tag.comment);
4072+
}
4073+
40624074
function emitJSDocNameReference(node: JSDocNameReference) {
40634075
writeSpace();
40644076
writePunctuation("{");

src/compiler/factory/nodeFactory.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ import {
250250
JSDocSatisfiesTag,
251251
JSDocSeeTag,
252252
JSDocSignature,
253+
JSDocSpecializeTag,
253254
JSDocTag,
254255
JSDocTemplateTag,
255256
JSDocText,
@@ -861,6 +862,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
861862
updateJSDocSeeTag,
862863
createJSDocImportTag,
863864
updateJSDocImportTag,
865+
createJSDocSpecializeTag,
866+
updateJSDocSpecializeTag,
864867
createJSDocNameReference,
865868
updateJSDocNameReference,
866869
createJSDocMemberName,
@@ -5552,6 +5555,22 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
55525555
: node;
55535556
}
55545557

5558+
// @api
5559+
function createJSDocSpecializeTag(tagName: Identifier | undefined, typeArguments: readonly TypeNode[], comment: string | NodeArray<JSDocComment> | undefined): JSDocSpecializeTag {
5560+
const node = createBaseJSDocTag<JSDocSpecializeTag>(SyntaxKind.JSDocSpecializeTag, tagName ?? createIdentifier("specialize"), /*comment*/ undefined);
5561+
node.typeArguments = asNodeArray(typeArguments);
5562+
node.comment = comment;
5563+
return node;
5564+
}
5565+
5566+
function updateJSDocSpecializeTag(node: JSDocSpecializeTag, tagName: Identifier | undefined, typeArguments: readonly TypeNode[], comment: string | NodeArray<JSDocComment> | undefined): JSDocSpecializeTag {
5567+
return node.tagName !== tagName
5568+
|| node.typeArguments !== typeArguments
5569+
|| node.comment !== comment
5570+
? update(createJSDocSpecializeTag(tagName, typeArguments, comment), node)
5571+
: node;
5572+
}
5573+
55555574
// @api
55565575
function createJSDocText(text: string): JSDocText {
55575576
const node = createBaseNode<JSDocText>(SyntaxKind.JSDocText);
@@ -7212,6 +7231,8 @@ function getDefaultTagNameForKind(kind: JSDocTag["kind"]): string {
72127231
return "implements";
72137232
case SyntaxKind.JSDocImportTag:
72147233
return "import";
7234+
case SyntaxKind.JSDocSpecializeTag:
7235+
return "specialize";
72157236
default:
72167237
return Debug.fail(`Unsupported kind: ${Debug.formatSyntaxKind(kind)}`);
72177238
}

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 */

0 commit comments

Comments
 (0)