Skip to content

Commit e796e7b

Browse files
committed
Check @overload tag against its implementation
Also, make @overload work like other jsdoc tags: only the last jsdoc tag before a declaration is used. That means all overload tags need to be in one tag: ```js /** * @overload * @return {number} * * @overload * @return {string} */ ``` function f() { return "1" } This no longer works: ```js /** * @overload * @return {number} */ /** * @overload * @return {string} */ function f() { return "2" } ``` Fixes microsoft#51234
1 parent 55ae4a3 commit e796e7b

13 files changed

+283
-93
lines changed

src/compiler/checker.ts

+13-20
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ import {
230230
getAliasDeclarationFromName,
231231
getAllAccessorDeclarations,
232232
getAllJSDocTags,
233+
getAllJSDocTagsOfKind,
233234
getAllowSyntheticDefaultImports,
234235
getAncestor,
235236
getAssignedExpandoInitializer,
@@ -279,6 +280,7 @@ import {
279280
getExternalModuleRequireArgument,
280281
getFirstConstructorWithBody,
281282
getFirstIdentifier,
283+
getFirstJSDocTag,
282284
getFunctionFlags,
283285
getHostSignatureFromJSDoc,
284286
getIdentifierGeneratedImportReference,
@@ -745,6 +747,7 @@ import {
745747
JSDocMemberName,
746748
JSDocNullableType,
747749
JSDocOptionalType,
750+
JSDocOverloadTag,
748751
JSDocParameterTag,
749752
JSDocPrivateTag,
750753
JSDocPropertyLikeTag,
@@ -14442,15 +14445,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1444214445
}
1444314446
if (isInJSFile(decl) && decl.jsDoc) {
1444414447
let hasJSDocOverloads = false;
14445-
for (const node of decl.jsDoc) {
14446-
if (node.tags) {
14447-
for (const tag of node.tags) {
14448-
if (isJSDocOverloadTag(tag)) {
14449-
result.push(getSignatureFromDeclaration(tag.typeExpression));
14450-
hasJSDocOverloads = true;
14451-
}
14452-
}
14453-
}
14448+
for (const tag of getAllJSDocTagsOfKind(decl, SyntaxKind.JSDocOverloadTag)) {
14449+
result.push(getSignatureFromDeclaration((tag as JSDocOverloadTag).typeExpression));
14450+
hasJSDocOverloads = true;
1445414451
}
1445514452
if (hasJSDocOverloads) {
1445614453
continue;
@@ -38584,20 +38581,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3858438581
}
3858538582
}
3858638583
if (isInJSFile(current) && isFunctionLike(current) && current.jsDoc) {
38587-
// TODO: De-duplicate tag check and improve error span
38588-
outer: for (const node of current.jsDoc) {
38589-
if (node.tags) {
38590-
for (const tag of node.tags) {
38591-
if (isJSDocOverloadTag(tag)) {
38592-
hasOverloads = true;
38593-
break outer;
38594-
}
38595-
}
38596-
}
38584+
if (getFirstJSDocTag(current, isJSDocOverloadTag)) {
38585+
hasOverloads = true;
3859738586
}
3859838587
}
3859938588
}
3860038589
}
38590+
3860138591
if (multipleConstructorImplementation) {
3860238592
forEach(functionDeclarations, declaration => {
3860338593
error(declaration, Diagnostics.Multiple_constructor_implementations_are_not_allowed);
@@ -38646,8 +38636,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3864638636
const bodySignature = getSignatureFromDeclaration(bodyDeclaration);
3864738637
for (const signature of signatures) {
3864838638
if (!isImplementationCompatibleWithOverload(bodySignature, signature)) {
38639+
const errorNode = signature.declaration && isJSDocSignature(signature.declaration)
38640+
? (signature.declaration.parent as JSDocOverloadTag | JSDocCallbackTag).tagName
38641+
: signature.declaration;
3864938642
addRelatedInfo(
38650-
error(signature.declaration, Diagnostics.This_overload_signature_is_not_compatible_with_its_implementation_signature),
38643+
error(errorNode, Diagnostics.This_overload_signature_is_not_compatible_with_its_implementation_signature),
3865138644
createDiagnosticForNode(bodyDeclaration, Diagnostics.The_implementation_signature_is_declared_here)
3865238645
);
3865338646
break;

src/compiler/utilitiesPublic.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1205,8 +1205,10 @@ export function getJSDocTagsNoCache(node: Node): readonly JSDocTag[] {
12051205
return getJSDocTagsWorker(node, /*noCache*/ true);
12061206
}
12071207

1208-
/** Get the first JSDoc tag of a specified kind, or undefined if not present. */
1209-
function getFirstJSDocTag<T extends JSDocTag>(node: Node, predicate: (tag: JSDocTag) => tag is T, noCache?: boolean): T | undefined {
1208+
/** @internal
1209+
* Get the first JSDoc tag of a specified kind, or undefined if not present.
1210+
*/
1211+
export function getFirstJSDocTag<T extends JSDocTag>(node: Node, predicate: (tag: JSDocTag) => tag is T, noCache?: boolean): T | undefined {
12101212
return find(getJSDocTagsWorker(node, noCache), predicate);
12111213
}
12121214

tests/baselines/reference/jsFileAlternativeUseOfOverloadTag.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,15 @@ var example3 = {
105105

106106
//// [jsFileAlternativeUseOfOverloadTag.d.ts]
107107
declare namespace example1 {
108-
function constructor(value: any, options: any): void;
108+
function constructor(value: any): any;
109109
}
110110
declare namespace example2 {
111-
export function constructor_1(): void;
111+
export function constructor_1(value: any, secretAccessKey: any, sessionToken: any): any;
112+
export function constructor_1(): any;
112113
export { constructor_1 as constructor };
113114
}
114115
declare namespace example3 {
115-
function evaluate(options: any, callback: any): void;
116+
function evaluate(): any;
116117
}
117118
/**
118119
* function (error, result)

tests/baselines/reference/jsFileAlternativeUseOfOverloadTag.types

+15-15
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,26 @@
44
// trying to make sure that our changes do not result in any crashes here.
55

66
const example1 = {
7-
>example1 : { constructor: (value: any, options: any) => void; }
8-
>{ /** * @overload Example1(value) * Creates Example1 * @param value [String] */ constructor: function Example1(value, options) {},} : { constructor: (value: any, options: any) => void; }
7+
>example1 : { constructor: (value: any) => any; }
8+
>{ /** * @overload Example1(value) * Creates Example1 * @param value [String] */ constructor: function Example1(value, options) {},} : { constructor: (value: any) => any; }
99

1010
/**
1111
* @overload Example1(value)
1212
* Creates Example1
1313
* @param value [String]
1414
*/
1515
constructor: function Example1(value, options) {},
16-
>constructor : (value: any, options: any) => void
17-
>function Example1(value, options) {} : (value: any, options: any) => void
18-
>Example1 : (value: any, options: any) => void
16+
>constructor : (value: any) => any
17+
>function Example1(value, options) {} : (value: any) => any
18+
>Example1 : (value: any) => any
1919
>value : any
2020
>options : any
2121

2222
};
2323

2424
const example2 = {
25-
>example2 : { constructor: () => void; }
26-
>{ /** * Example 2 * * @overload Example2(value) * Creates Example2 * @param value [String] * @param secretAccessKey [String] * @param sessionToken [String] * @example Creates with string value * const example = new Example(''); * @overload Example2(options) * Creates Example2 * @option options value [String] * @example Creates with options object * const example = new Example2({ * value: '', * }); */ constructor: function Example2() {},} : { constructor: () => void; }
25+
>example2 : { constructor: { (value: any, secretAccessKey: any, sessionToken: any): any; (): any; }; }
26+
>{ /** * Example 2 * * @overload Example2(value) * Creates Example2 * @param value [String] * @param secretAccessKey [String] * @param sessionToken [String] * @example Creates with string value * const example = new Example(''); * @overload Example2(options) * Creates Example2 * @option options value [String] * @example Creates with options object * const example = new Example2({ * value: '', * }); */ constructor: function Example2() {},} : { constructor: { (value: any, secretAccessKey: any, sessionToken: any): any; (): any; }; }
2727

2828
/**
2929
* Example 2
@@ -44,15 +44,15 @@ const example2 = {
4444
* });
4545
*/
4646
constructor: function Example2() {},
47-
>constructor : () => void
48-
>function Example2() {} : () => void
49-
>Example2 : () => void
47+
>constructor : { (value: any, secretAccessKey: any, sessionToken: any): any; (): any; }
48+
>function Example2() {} : { (value: any, secretAccessKey: any, sessionToken: any): any; (): any; }
49+
>Example2 : { (value: any, secretAccessKey: any, sessionToken: any): any; (): any; }
5050

5151
};
5252

5353
const example3 = {
54-
>example3 : { evaluate: (options: any, callback: any) => void; }
55-
>{ /** * @overload evaluate(options = {}, [callback]) * Evaluate something * @note Something interesting * @param options [map] * @return [string] returns evaluation result * @return [null] returns nothing if callback provided * @callback callback function (error, result) * If callback is provided it will be called with evaluation result * @param error [Error] * @param result [String] * @see callback */ evaluate: function evaluate(options, callback) {},} : { evaluate: (options: any, callback: any) => void; }
54+
>example3 : { evaluate: () => any; }
55+
>{ /** * @overload evaluate(options = {}, [callback]) * Evaluate something * @note Something interesting * @param options [map] * @return [string] returns evaluation result * @return [null] returns nothing if callback provided * @callback callback function (error, result) * If callback is provided it will be called with evaluation result * @param error [Error] * @param result [String] * @see callback */ evaluate: function evaluate(options, callback) {},} : { evaluate: () => any; }
5656

5757
/**
5858
* @overload evaluate(options = {}, [callback])
@@ -68,9 +68,9 @@ const example3 = {
6868
* @see callback
6969
*/
7070
evaluate: function evaluate(options, callback) {},
71-
>evaluate : (options: any, callback: any) => void
72-
>function evaluate(options, callback) {} : (options: any, callback: any) => void
73-
>evaluate : (options: any, callback: any) => void
71+
>evaluate : () => any
72+
>function evaluate(options, callback) {} : () => any
73+
>evaluate : () => any
7474
>options : any
7575
>callback : any
7676

tests/baselines/reference/jsFileFunctionOverloads.js

+40-5
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,46 @@ function flatMap(array, iterable) {
118118

119119

120120
//// [jsFileFunctionOverloads.d.ts]
121-
declare function getTypeName(x: number): 'number';
122-
declare function getTypeName(x: string): 'string';
123-
declare function getTypeName(x: boolean): 'boolean';
124-
declare function flatMap<T, U>(array: T[], iterable: (x: T) => U[]): U[];
125-
declare function flatMap<T>(array: T[][]): T[];
121+
/**
122+
* @overload
123+
* @param {number} x
124+
* @returns {'number'}
125+
*/
126+
/**
127+
* @overload
128+
* @param {string} x
129+
* @returns {'string'}
130+
*/
131+
/**
132+
* @overload
133+
* @param {boolean} x
134+
* @returns {'boolean'}
135+
*/
136+
/**
137+
* @param {unknown} x
138+
* @returns {string}
139+
*/
140+
declare function getTypeName(x: unknown): string;
141+
/**
142+
* @template T
143+
* @template U
144+
* @overload
145+
* @param {T[]} array
146+
* @param {(x: T) => U[]} iterable
147+
* @returns {U[]}
148+
*/
149+
/**
150+
* @template T
151+
* @overload
152+
* @param {T[][]} array
153+
* @returns {T[]}
154+
*/
155+
/**
156+
* @param {unknown[]} array
157+
* @param {(x: unknown) => unknown} iterable
158+
* @returns {unknown[]}
159+
*/
160+
declare function flatMap(array: unknown[], iterable?: (x: unknown) => unknown): unknown[];
126161
/**
127162
* @template T
128163
* @param {T} x

tests/baselines/reference/jsFileFunctionOverloads.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
* @returns {string}
2020
*/
2121
function getTypeName(x) {
22-
>getTypeName : { (x: number): 'number'; (x: string): 'string'; (x: boolean): 'boolean'; }
22+
>getTypeName : (x: unknown) => string
2323
>x : unknown
2424

2525
return typeof x;
@@ -58,7 +58,7 @@ const identity = x => x;
5858
* @returns {unknown[]}
5959
*/
6060
function flatMap(array, iterable = identity) {
61-
>flatMap : { <T, U>(array: T[], iterable: (x: T) => U[]): U[]; <T>(array: T[][]): T[]; }
61+
>flatMap : (array: unknown[], iterable?: (x: unknown) => unknown) => unknown[]
6262
>array : unknown[]
6363
>iterable : (x: unknown) => unknown
6464
>identity : <T>(x: T) => T

tests/baselines/reference/jsFileMethodOverloads.js

+29-4
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,33 @@ declare class Example<T> {
105105
*/
106106
constructor(value: T);
107107
value: T;
108-
getTypeName(this: Example<number>): 'number';
109-
getTypeName(this: Example<string>): 'string';
110-
transform<U>(fn: (y: T) => U): U;
111-
transform(): T;
108+
/**
109+
* @overload
110+
* @param {Example<number>} this
111+
* @returns {'number'}
112+
*/
113+
/**
114+
* @overload
115+
* @param {Example<string>} this
116+
* @returns {'string'}
117+
*/
118+
/**
119+
* @returns {string}
120+
*/
121+
getTypeName(): string;
122+
/**
123+
* @template U
124+
* @overload
125+
* @param {(y: T) => U} fn
126+
* @returns {U}
127+
*/
128+
/**
129+
* @overload
130+
* @returns {T}
131+
*/
132+
/**
133+
* @param {(y: T) => unknown} [fn]
134+
* @returns {unknown}
135+
*/
136+
transform(fn?: (y: T) => unknown): unknown;
112137
}

tests/baselines/reference/jsFileMethodOverloads.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
* @returns {string}
3434
*/
3535
getTypeName() {
36-
>getTypeName : { (this: Example<number>): 'number'; (this: Example<string>): 'string'; }
36+
>getTypeName : () => string
3737

3838
return typeof this.value;
3939
>typeof this.value : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
@@ -57,7 +57,7 @@
5757
* @returns {unknown}
5858
*/
5959
transform(fn) {
60-
>transform : { <U>(fn: (y: T) => U): U; (): T; }
60+
>transform : (fn?: (y: T) => unknown) => unknown
6161
>fn : (y: T) => unknown
6262

6363
return fn ? fn(this.value) : this.value;

tests/baselines/reference/overloadTag1.errors.txt

+38-7
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,31 @@
1-
tests/cases/conformance/jsdoc/overloadTag1.js(27,1): error TS2769: No overload matches this call.
1+
tests/cases/conformance/jsdoc/overloadTag1.js(7,5): error TS2394: This overload signature is not compatible with its implementation signature.
2+
tests/cases/conformance/jsdoc/overloadTag1.js(25,1): error TS2769: No overload matches this call.
3+
Overload 1 of 2, '(a: number, b: number): number', gave the following error.
4+
Argument of type 'string' is not assignable to parameter of type 'number'.
5+
Overload 2 of 2, '(a: string, b: boolean): string', gave the following error.
6+
Argument of type 'string' is not assignable to parameter of type 'boolean'.
7+
tests/cases/conformance/jsdoc/overloadTag1.js(42,1): error TS2769: No overload matches this call.
28
Overload 1 of 2, '(a: number, b: number): number', gave the following error.
39
Argument of type 'string' is not assignable to parameter of type 'number'.
410
Overload 2 of 2, '(a: string, b: boolean): string', gave the following error.
511
Argument of type 'string' is not assignable to parameter of type 'boolean'.
612

713

8-
==== tests/cases/conformance/jsdoc/overloadTag1.js (1 errors) ====
14+
==== tests/cases/conformance/jsdoc/overloadTag1.js (3 errors) ====
915
/**
1016
* @overload
1117
* @param {number} a
1218
* @param {number} b
1319
* @returns {number}
14-
*/
15-
/**
20+
*
1621
* @overload
22+
~~~~~~~~
23+
!!! error TS2394: This overload signature is not compatible with its implementation signature.
24+
!!! related TS2750 tests/cases/conformance/jsdoc/overloadTag1.js:16:17: The implementation signature is declared here.
1725
* @param {string} a
1826
* @param {boolean} b
1927
* @returns {string}
20-
*/
21-
/**
28+
*
2229
* @param {string | number} a
2330
* @param {string | number} b
2431
* @returns {string | number}
@@ -38,4 +45,28 @@ tests/cases/conformance/jsdoc/overloadTag1.js(27,1): error TS2769: No overload m
3845
!!! error TS2769: Overload 1 of 2, '(a: number, b: number): number', gave the following error.
3946
!!! error TS2769: Argument of type 'string' is not assignable to parameter of type 'number'.
4047
!!! error TS2769: Overload 2 of 2, '(a: string, b: boolean): string', gave the following error.
41-
!!! error TS2769: Argument of type 'string' is not assignable to parameter of type 'boolean'.
48+
!!! error TS2769: Argument of type 'string' is not assignable to parameter of type 'boolean'.
49+
50+
/**
51+
* @overload
52+
* @param {number} a
53+
* @param {number} b
54+
* @returns {number}
55+
*
56+
* @overload
57+
* @param {string} a
58+
* @param {boolean} b
59+
* @returns {string}
60+
*/
61+
export function uncheckedInternally(a, b) {
62+
return a + b;
63+
}
64+
uncheckedInternally(1,2)
65+
uncheckedInternally("zero", "one")
66+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
67+
!!! error TS2769: No overload matches this call.
68+
!!! error TS2769: Overload 1 of 2, '(a: number, b: number): number', gave the following error.
69+
!!! error TS2769: Argument of type 'string' is not assignable to parameter of type 'number'.
70+
!!! error TS2769: Overload 2 of 2, '(a: string, b: boolean): string', gave the following error.
71+
!!! error TS2769: Argument of type 'string' is not assignable to parameter of type 'boolean'.
72+

0 commit comments

Comments
 (0)