Skip to content

Commit 83a1bb2

Browse files
committed
Add support for "specialize" tag
1 parent 41b993b commit 83a1bb2

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
@@ -238,6 +238,7 @@ import {
238238
JSDocParameterTag,
239239
JSDocPropertyLikeTag,
240240
JSDocSignature,
241+
JSDocSpecializeTag,
241242
JSDocTypedefTag,
242243
JSDocTypeLiteral,
243244
JsxAttribute,
@@ -3076,6 +3077,8 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
30763077
return bind((node as JSDocOverloadTag).typeExpression);
30773078
case SyntaxKind.JSDocImportTag:
30783079
return (jsDocImports || (jsDocImports = [])).push(node as JSDocImportTag);
3080+
case SyntaxKind.JSDocSpecializeTag:
3081+
return bindEach((node as JSDocSpecializeTag).typeArguments);
30793082
}
30803083
}
30813084

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,
@@ -827,7 +828,6 @@ import {
827828
JsxFlags,
828829
JsxFragment,
829830
JsxNamespacedName,
830-
JsxOpeningElement,
831831
JsxOpeningFragment,
832832
JsxOpeningLikeElement,
833833
JsxReferenceKind,
@@ -34636,15 +34636,34 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3463634636
return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, getNodeLinks(node).resolvedSymbol, indexedAccessType, indexExpression, checkMode), node);
3463734637
}
3463834638

34639-
function callLikeExpressionMayHaveTypeArguments(node: CallLikeExpression): node is CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement {
34639+
function callLikeExpressionMayHaveTypeArguments(node: CallLikeExpression): node is CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningLikeElement {
3464034640
return isCallOrNewExpression(node) || isTaggedTemplateExpression(node) || isJsxOpeningLikeElement(node);
3464134641
}
3464234642

34643+
function getTypeArgumentsForCallLikeExpression(node: CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningLikeElement) {
34644+
if (isSuperCall(node)) {
34645+
return undefined;
34646+
}
34647+
if (isInJSFile(node)) {
34648+
let { parent } = node;
34649+
if (isJsxElement(parent)) {
34650+
parent = parent.parent;
34651+
}
34652+
if (canHaveJSDoc(parent)) {
34653+
const specializeTag = getJSDocSpecializeTag(parent);
34654+
if (specializeTag) {
34655+
return specializeTag.typeArguments;
34656+
}
34657+
}
34658+
}
34659+
return node.typeArguments;
34660+
}
34661+
3464334662
function resolveUntypedCall(node: CallLikeExpression): Signature {
3464434663
if (callLikeExpressionMayHaveTypeArguments(node)) {
3464534664
// Check type arguments even though we will give an error that untyped calls may not accept type arguments.
3464634665
// This gets us diagnostics for the type arguments and marks them as referenced.
34647-
forEach(node.typeArguments, checkSourceElement);
34666+
forEach(getTypeArgumentsForCallLikeExpression(node), checkSourceElement);
3464834667
}
3464934668

3465034669
if (node.kind === SyntaxKind.TaggedTemplateExpression) {
@@ -35595,21 +35614,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3559535614
}
3559635615

3559735616
function resolveCall(node: CallLikeExpression, signatures: readonly Signature[], candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, callChainFlags: SignatureFlags, headMessage?: DiagnosticMessage): Signature {
35598-
const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression;
3559935617
const isDecorator = node.kind === SyntaxKind.Decorator;
35600-
const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node);
3560135618
const isInstanceof = node.kind === SyntaxKind.BinaryExpression;
3560235619
const reportErrors = !isInferencePartiallyBlocked && !candidatesOutArray;
3560335620

3560435621
let typeArguments: NodeArray<TypeNode> | undefined;
3560535622

35606-
if (!isDecorator && !isInstanceof && !isSuperCall(node)) {
35607-
typeArguments = (node as CallExpression).typeArguments;
35608-
35609-
// We already perform checking on the type arguments on the class declaration itself.
35610-
if (isTaggedTemplate || isJsxOpeningOrSelfClosingElement || (node as CallExpression).expression.kind !== SyntaxKind.SuperKeyword) {
35611-
forEach(typeArguments, checkSourceElement);
35612-
}
35623+
if (callLikeExpressionMayHaveTypeArguments(node)) {
35624+
typeArguments = getTypeArgumentsForCallLikeExpression(node);
35625+
forEach(typeArguments, checkSourceElement);
3561335626
}
3561435627

3561535628
const candidates = candidatesOutArray || [];
@@ -35781,7 +35794,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3578135794
diagnostics.add(getArgumentArityError(node, [candidateForArgumentArityError], args, headMessage));
3578235795
}
3578335796
else if (candidateForTypeArgumentError) {
35784-
checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression | TaggedTemplateExpression | JsxOpeningLikeElement).typeArguments!, /*reportErrors*/ true, headMessage);
35797+
checkTypeArguments(candidateForTypeArgumentError, typeArguments!, /*reportErrors*/ true, headMessage);
3578535798
}
3578635799
else {
3578735800
const signaturesWithCorrectTypeArgumentArity = filter(signatures, s => hasCorrectTypeArgumentArity(s, typeArguments));
@@ -35995,7 +36008,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3599536008
return candidate;
3599636009
}
3599736010

35998-
const typeArgumentNodes: readonly TypeNode[] | undefined = callLikeExpressionMayHaveTypeArguments(node) ? node.typeArguments : undefined;
36011+
const typeArgumentNodes: readonly TypeNode[] | undefined = callLikeExpressionMayHaveTypeArguments(node) ? getTypeArgumentsForCallLikeExpression(node) : undefined;
3599936012
const instantiated = typeArgumentNodes
3600036013
? createSignatureInstantiation(candidate, getTypeArgumentsFromNodes(typeArgumentNodes, typeParameters, isInJSFile(node)))
3600136014
: inferSignatureInstantiationForOverloadFailure(node, typeParameters, candidate, args, checkMode);
@@ -36095,14 +36108,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3609536108
// that the user will not add any.
3609636109
const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call);
3609736110
const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length;
36111+
const typeArguments = getTypeArgumentsForCallLikeExpression(node);
3609836112

3609936113
// TS 1.0 Spec: 4.12
3610036114
// In an untyped function call no TypeArgs are permitted, Args can be any argument list, no contextual
3610136115
// types are provided for the argument expressions, and the result is always of type Any.
3610236116
if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) {
3610336117
// The unknownType indicates that an error already occurred (and was reported). No
3610436118
// need to report another error in this case.
36105-
if (!isErrorType(funcType) && node.typeArguments) {
36119+
if (!isErrorType(funcType) && typeArguments) {
3610636120
error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments);
3610736121
}
3610836122
return resolveUntypedCall(node);
@@ -36138,7 +36152,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3613836152
// use the resolvingSignature singleton to indicate that we deferred processing. This result will be
3613936153
// propagated out and eventually turned into silentNeverType (a type that is assignable to anything and
3614036154
// from which we never make inferences).
36141-
if (checkMode & CheckMode.SkipGenericFunctions && !node.typeArguments && callSignatures.some(isGenericFunctionReturningFunction)) {
36155+
if (checkMode & CheckMode.SkipGenericFunctions && !typeArguments && callSignatures.some(isGenericFunctionReturningFunction)) {
3614236156
skippedGenericFunction(node, checkMode);
3614336157
return resolvingSignature;
3614436158
}
@@ -36183,11 +36197,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3618336197
return resolveErrorCall(node);
3618436198
}
3618536199

36200+
const typeArguments = getTypeArgumentsForCallLikeExpression(node);
3618636201
// TS 1.0 spec: 4.11
3618736202
// If expressionType is of type Any, Args can be any argument
3618836203
// list and the result of the operation is of type Any.
3618936204
if (isTypeAny(expressionType)) {
36190-
if (node.typeArguments) {
36205+
if (typeArguments) {
3619136206
error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments);
3619236207
}
3619336208
return resolveUntypedCall(node);
@@ -36552,9 +36567,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3655236567
const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node);
3655336568
const fakeSignature = createSignatureForJSXIntrinsic(node, result);
3655436569
checkTypeAssignableToAndOptionallyElaborate(checkExpressionWithContextualType(node.attributes, getEffectiveFirstArgumentForJsxSignature(fakeSignature, node), /*inferenceContext*/ undefined, CheckMode.Normal), result, node.tagName, node.attributes);
36555-
if (length(node.typeArguments)) {
36556-
forEach(node.typeArguments, checkSourceElement);
36557-
diagnostics.add(createDiagnosticForNodeArray(getSourceFileOfNode(node), node.typeArguments!, Diagnostics.Expected_0_type_arguments_but_got_1, 0, length(node.typeArguments)));
36570+
const typeArguments = getTypeArgumentsForCallLikeExpression(node);
36571+
if (length(typeArguments)) {
36572+
forEach(typeArguments, checkSourceElement);
36573+
diagnostics.add(createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments!, Diagnostics.Expected_0_type_arguments_but_got_1, 0, length(typeArguments)));
3655836574
}
3655936575
return fakeSignature;
3656036576
}
@@ -36810,7 +36826,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3681036826
* @returns On success, the expression's signature's return type. On failure, anyType.
3681136827
*/
3681236828
function checkCallExpression(node: CallExpression | NewExpression, checkMode?: CheckMode): Type {
36813-
checkGrammarTypeArguments(node, node.typeArguments);
36829+
checkGrammarTypeArguments(node, getTypeArgumentsForCallLikeExpression(node));
3681436830

3681536831
const signature = getResolvedSignature(node, /*candidatesOutArray*/ undefined, checkMode);
3681636832
if (signature === resolvingSignature) {
@@ -37049,7 +37065,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3704937065
}
3705037066

3705137067
function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type {
37052-
if (!checkGrammarTaggedTemplateChain(node)) checkGrammarTypeArguments(node, node.typeArguments);
37068+
if (!checkGrammarTaggedTemplateChain(node)) {
37069+
checkGrammarTypeArguments(node, getTypeArgumentsForCallLikeExpression(node));
37070+
}
3705337071
if (languageVersion < LanguageFeatureMinimumTarget.TaggedTemplates) {
3705437072
checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject);
3705537073
}
@@ -41595,15 +41613,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4159541613
checkDecorators(node);
4159641614
}
4159741615

41598-
function getEffectiveTypeArgumentAtIndex(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[], index: number): Type {
41616+
function getEffectiveTypeArgumentAtIndex(node: TypeReferenceNode, typeParameters: readonly TypeParameter[], index: number): Type {
4159941617
if (node.typeArguments && index < node.typeArguments.length) {
4160041618
return getTypeFromTypeNode(node.typeArguments[index]);
4160141619
}
4160241620
return getEffectiveTypeArguments(node, typeParameters)[index];
4160341621
}
4160441622

4160541623
function getEffectiveTypeArguments(node: TypeReferenceNode | ExpressionWithTypeArguments | NodeWithTypeArguments, typeParameters: readonly TypeParameter[]): Type[] {
41606-
return fillMissingTypeArguments(map(node.typeArguments!, getTypeFromTypeNode), typeParameters, getMinTypeArgumentCount(typeParameters), isInJSFile(node));
41624+
return fillMissingTypeArguments(map(node.typeArguments || [], getTypeFromTypeNode), typeParameters, getMinTypeArgumentCount(typeParameters), isInJSFile(node));
4160741625
}
4160841626

4160941627
function checkTypeArgumentConstraints(node: TypeReferenceNode | ExpressionWithTypeArguments | NodeWithTypeArguments, typeParameters: readonly TypeParameter[]): boolean {
@@ -51341,7 +51359,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
5134151359

5134251360
function checkGrammarJsxElement(node: JsxOpeningLikeElement) {
5134351361
checkGrammarJsxName(node.tagName);
51344-
checkGrammarTypeArguments(node, node.typeArguments);
51362+
checkGrammarTypeArguments(node, getTypeArgumentsForCallLikeExpression(node));
5134551363
const seen = new Map<__String, boolean>();
5134651364

5134751365
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,
@@ -5547,6 +5550,22 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
55475550
: node;
55485551
}
55495552

5553+
// @api
5554+
function createJSDocSpecializeTag(tagName: Identifier | undefined, typeArguments: readonly TypeNode[], comment: string | NodeArray<JSDocComment> | undefined): JSDocSpecializeTag {
5555+
const node = createBaseJSDocTag<JSDocSpecializeTag>(SyntaxKind.JSDocSpecializeTag, tagName ?? createIdentifier("specialize"), /*comment*/ undefined);
5556+
node.typeArguments = asNodeArray(typeArguments);
5557+
node.comment = comment;
5558+
return node;
5559+
}
5560+
5561+
function updateJSDocSpecializeTag(node: JSDocSpecializeTag, tagName: Identifier | undefined, typeArguments: readonly TypeNode[], comment: string | NodeArray<JSDocComment> | undefined): JSDocSpecializeTag {
5562+
return node.tagName !== tagName
5563+
|| node.typeArguments !== typeArguments
5564+
|| node.comment !== comment
5565+
? update(createJSDocSpecializeTag(tagName, typeArguments, comment), node)
5566+
: node;
5567+
}
5568+
55505569
// @api
55515570
function createJSDocText(text: string): JSDocText {
55525571
const node = createBaseNode<JSDocText>(SyntaxKind.JSDocText);
@@ -7205,6 +7224,8 @@ function getDefaultTagNameForKind(kind: JSDocTag["kind"]): string {
72057224
return "implements";
72067225
case SyntaxKind.JSDocImportTag:
72077226
return "import";
7227+
case SyntaxKind.JSDocSpecializeTag:
7228+
return "specialize";
72087229
default:
72097230
return Debug.fail(`Unsupported kind: ${Debug.formatSyntaxKind(kind)}`);
72107231
}

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)