diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6370cd13625f3..fed4bffa0b2ad 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -747,6 +747,7 @@ import { JSDocMemberName, JSDocNullableType, JSDocOptionalType, + JSDocOverloadTag, JSDocParameterTag, JSDocPrivateTag, JSDocPropertyLikeTag, @@ -38616,6 +38617,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { lastSeenNonAmbientDeclaration = node as FunctionLikeDeclaration; } } + if (isInJSFile(current) && isFunctionLike(current) && current.jsDoc) { + for (const node of current.jsDoc) { + if (node.tags) { + for (const tag of node.tags) { + if (isJSDocOverloadTag(tag)) { + hasOverloads = true; + } + } + } + } + } } } @@ -38667,8 +38679,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const bodySignature = getSignatureFromDeclaration(bodyDeclaration); for (const signature of signatures) { if (!isImplementationCompatibleWithOverload(bodySignature, signature)) { + const errorNode = signature.declaration && isJSDocSignature(signature.declaration) + ? (signature.declaration.parent as JSDocOverloadTag | JSDocCallbackTag).tagName + : signature.declaration; addRelatedInfo( - error(signature.declaration, Diagnostics.This_overload_signature_is_not_compatible_with_its_implementation_signature), + error(errorNode, Diagnostics.This_overload_signature_is_not_compatible_with_its_implementation_signature), createDiagnosticForNode(bodyDeclaration, Diagnostics.The_implementation_signature_is_declared_here) ); break; diff --git a/tests/baselines/reference/overloadTag1.errors.txt b/tests/baselines/reference/overloadTag1.errors.txt new file mode 100644 index 0000000000000..faccaf62d16c9 --- /dev/null +++ b/tests/baselines/reference/overloadTag1.errors.txt @@ -0,0 +1,73 @@ +tests/cases/conformance/jsdoc/overloadTag1.js(7,5): error TS2394: This overload signature is not compatible with its implementation signature. +tests/cases/conformance/jsdoc/overloadTag1.js(25,10): error TS2769: No overload matches this call. + Overload 1 of 2, '(a: number, b: number): number', gave the following error. + Argument of type 'string' is not assignable to parameter of type 'number'. + Overload 2 of 2, '(a: string, b: boolean): string', gave the following error. + Argument of type 'string' is not assignable to parameter of type 'boolean'. +tests/cases/conformance/jsdoc/overloadTag1.js(43,1): error TS2769: No overload matches this call. + Overload 1 of 2, '(a: number, b: number): number', gave the following error. + Argument of type 'string' is not assignable to parameter of type 'number'. + Overload 2 of 2, '(a: string, b: boolean): string', gave the following error. + Argument of type 'string' is not assignable to parameter of type 'boolean'. + + +==== tests/cases/conformance/jsdoc/overloadTag1.js (3 errors) ==== + /** + * @overload + * @param {number} a + * @param {number} b + * @returns {number} + * + * @overload + ~~~~~~~~ +!!! error TS2394: This overload signature is not compatible with its implementation signature. +!!! related TS2750 tests/cases/conformance/jsdoc/overloadTag1.js:16:17: The implementation signature is declared here. + * @param {string} a + * @param {boolean} b + * @returns {string} + * + * @param {string | number} a + * @param {string | number} b + * @returns {string | number} + */ + export function overloaded(a,b) { + if (typeof a === "string" && typeof b === "string") { + return a + b; + } else if (typeof a === "number" && typeof b === "number") { + return a + b; + } + throw new Error("Invalid arguments"); + } + var o1 = overloaded(1,2) + var o2 = overloaded("zero", "one") + ~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2769: No overload matches this call. +!!! error TS2769: Overload 1 of 2, '(a: number, b: number): number', gave the following error. +!!! error TS2769: Argument of type 'string' is not assignable to parameter of type 'number'. +!!! error TS2769: Overload 2 of 2, '(a: string, b: boolean): string', gave the following error. +!!! error TS2769: Argument of type 'string' is not assignable to parameter of type 'boolean'. + var o3 = overloaded("a",false) + + /** + * @overload + * @param {number} a + * @param {number} b + * @returns {number} + * + * @overload + * @param {string} a + * @param {boolean} b + * @returns {string} + */ + export function uncheckedInternally(a, b) { + return a + b; + } + uncheckedInternally(1,2) + uncheckedInternally("zero", "one") + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2769: No overload matches this call. +!!! error TS2769: Overload 1 of 2, '(a: number, b: number): number', gave the following error. +!!! error TS2769: Argument of type 'string' is not assignable to parameter of type 'number'. +!!! error TS2769: Overload 2 of 2, '(a: string, b: boolean): string', gave the following error. +!!! error TS2769: Argument of type 'string' is not assignable to parameter of type 'boolean'. + \ No newline at end of file diff --git a/tests/baselines/reference/overloadTag1.js b/tests/baselines/reference/overloadTag1.js new file mode 100644 index 0000000000000..ddd5b29778b6e --- /dev/null +++ b/tests/baselines/reference/overloadTag1.js @@ -0,0 +1,102 @@ +//// [overloadTag1.js] +/** + * @overload + * @param {number} a + * @param {number} b + * @returns {number} + * + * @overload + * @param {string} a + * @param {boolean} b + * @returns {string} + * + * @param {string | number} a + * @param {string | number} b + * @returns {string | number} + */ +export function overloaded(a,b) { + if (typeof a === "string" && typeof b === "string") { + return a + b; + } else if (typeof a === "number" && typeof b === "number") { + return a + b; + } + throw new Error("Invalid arguments"); +} +var o1 = overloaded(1,2) +var o2 = overloaded("zero", "one") +var o3 = overloaded("a",false) + +/** + * @overload + * @param {number} a + * @param {number} b + * @returns {number} + * + * @overload + * @param {string} a + * @param {boolean} b + * @returns {string} + */ +export function uncheckedInternally(a, b) { + return a + b; +} +uncheckedInternally(1,2) +uncheckedInternally("zero", "one") + + +//// [overloadTag1.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.uncheckedInternally = exports.overloaded = void 0; +/** + * @overload + * @param {number} a + * @param {number} b + * @returns {number} + * + * @overload + * @param {string} a + * @param {boolean} b + * @returns {string} + * + * @param {string | number} a + * @param {string | number} b + * @returns {string | number} + */ +function overloaded(a, b) { + if (typeof a === "string" && typeof b === "string") { + return a + b; + } + else if (typeof a === "number" && typeof b === "number") { + return a + b; + } + throw new Error("Invalid arguments"); +} +exports.overloaded = overloaded; +var o1 = overloaded(1, 2); +var o2 = overloaded("zero", "one"); +var o3 = overloaded("a", false); +/** + * @overload + * @param {number} a + * @param {number} b + * @returns {number} + * + * @overload + * @param {string} a + * @param {boolean} b + * @returns {string} + */ +function uncheckedInternally(a, b) { + return a + b; +} +exports.uncheckedInternally = uncheckedInternally; +uncheckedInternally(1, 2); +uncheckedInternally("zero", "one"); + + +//// [overloadTag1.d.ts] +export function overloaded(a: number, b: number): number; +export function overloaded(a: string, b: boolean): string; +export function uncheckedInternally(a: number, b: number): number; +export function uncheckedInternally(a: string, b: boolean): string; diff --git a/tests/baselines/reference/overloadTag1.symbols b/tests/baselines/reference/overloadTag1.symbols new file mode 100644 index 0000000000000..9e53fb21dcec5 --- /dev/null +++ b/tests/baselines/reference/overloadTag1.symbols @@ -0,0 +1,78 @@ +=== tests/cases/conformance/jsdoc/overloadTag1.js === +/** + * @overload + * @param {number} a + * @param {number} b + * @returns {number} + * + * @overload + * @param {string} a + * @param {boolean} b + * @returns {string} + * + * @param {string | number} a + * @param {string | number} b + * @returns {string | number} + */ +export function overloaded(a,b) { +>overloaded : Symbol(overloaded, Decl(overloadTag1.js, 0, 0)) +>a : Symbol(a, Decl(overloadTag1.js, 15, 27)) +>b : Symbol(b, Decl(overloadTag1.js, 15, 29)) + + if (typeof a === "string" && typeof b === "string") { +>a : Symbol(a, Decl(overloadTag1.js, 15, 27)) +>b : Symbol(b, Decl(overloadTag1.js, 15, 29)) + + return a + b; +>a : Symbol(a, Decl(overloadTag1.js, 15, 27)) +>b : Symbol(b, Decl(overloadTag1.js, 15, 29)) + + } else if (typeof a === "number" && typeof b === "number") { +>a : Symbol(a, Decl(overloadTag1.js, 15, 27)) +>b : Symbol(b, Decl(overloadTag1.js, 15, 29)) + + return a + b; +>a : Symbol(a, Decl(overloadTag1.js, 15, 27)) +>b : Symbol(b, Decl(overloadTag1.js, 15, 29)) + } + throw new Error("Invalid arguments"); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +} +var o1 = overloaded(1,2) +>o1 : Symbol(o1, Decl(overloadTag1.js, 23, 3)) +>overloaded : Symbol(overloaded, Decl(overloadTag1.js, 0, 0)) + +var o2 = overloaded("zero", "one") +>o2 : Symbol(o2, Decl(overloadTag1.js, 24, 3)) +>overloaded : Symbol(overloaded, Decl(overloadTag1.js, 0, 0)) + +var o3 = overloaded("a",false) +>o3 : Symbol(o3, Decl(overloadTag1.js, 25, 3)) +>overloaded : Symbol(overloaded, Decl(overloadTag1.js, 0, 0)) + +/** + * @overload + * @param {number} a + * @param {number} b + * @returns {number} + * + * @overload + * @param {string} a + * @param {boolean} b + * @returns {string} + */ +export function uncheckedInternally(a, b) { +>uncheckedInternally : Symbol(uncheckedInternally, Decl(overloadTag1.js, 25, 30)) +>a : Symbol(a, Decl(overloadTag1.js, 38, 36)) +>b : Symbol(b, Decl(overloadTag1.js, 38, 38)) + + return a + b; +>a : Symbol(a, Decl(overloadTag1.js, 38, 36)) +>b : Symbol(b, Decl(overloadTag1.js, 38, 38)) +} +uncheckedInternally(1,2) +>uncheckedInternally : Symbol(uncheckedInternally, Decl(overloadTag1.js, 25, 30)) + +uncheckedInternally("zero", "one") +>uncheckedInternally : Symbol(uncheckedInternally, Decl(overloadTag1.js, 25, 30)) + diff --git a/tests/baselines/reference/overloadTag1.types b/tests/baselines/reference/overloadTag1.types new file mode 100644 index 0000000000000..647eb762b819f --- /dev/null +++ b/tests/baselines/reference/overloadTag1.types @@ -0,0 +1,112 @@ +=== tests/cases/conformance/jsdoc/overloadTag1.js === +/** + * @overload + * @param {number} a + * @param {number} b + * @returns {number} + * + * @overload + * @param {string} a + * @param {boolean} b + * @returns {string} + * + * @param {string | number} a + * @param {string | number} b + * @returns {string | number} + */ +export function overloaded(a,b) { +>overloaded : { (a: number, b: number): number; (a: string, b: boolean): string; } +>a : string | number +>b : string | number + + if (typeof a === "string" && typeof b === "string") { +>typeof a === "string" && typeof b === "string" : boolean +>typeof a === "string" : boolean +>typeof a : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>a : string | number +>"string" : "string" +>typeof b === "string" : boolean +>typeof b : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>b : string | number +>"string" : "string" + + return a + b; +>a + b : string +>a : string +>b : string + + } else if (typeof a === "number" && typeof b === "number") { +>typeof a === "number" && typeof b === "number" : boolean +>typeof a === "number" : boolean +>typeof a : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>a : string | number +>"number" : "number" +>typeof b === "number" : boolean +>typeof b : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>b : string | number +>"number" : "number" + + return a + b; +>a + b : number +>a : number +>b : number + } + throw new Error("Invalid arguments"); +>new Error("Invalid arguments") : Error +>Error : ErrorConstructor +>"Invalid arguments" : "Invalid arguments" +} +var o1 = overloaded(1,2) +>o1 : number +>overloaded(1,2) : number +>overloaded : { (a: number, b: number): number; (a: string, b: boolean): string; } +>1 : 1 +>2 : 2 + +var o2 = overloaded("zero", "one") +>o2 : never +>overloaded("zero", "one") : never +>overloaded : { (a: number, b: number): number; (a: string, b: boolean): string; } +>"zero" : "zero" +>"one" : "one" + +var o3 = overloaded("a",false) +>o3 : string +>overloaded("a",false) : string +>overloaded : { (a: number, b: number): number; (a: string, b: boolean): string; } +>"a" : "a" +>false : false + +/** + * @overload + * @param {number} a + * @param {number} b + * @returns {number} + * + * @overload + * @param {string} a + * @param {boolean} b + * @returns {string} + */ +export function uncheckedInternally(a, b) { +>uncheckedInternally : { (a: number, b: number): number; (a: string, b: boolean): string; } +>a : any +>b : any + + return a + b; +>a + b : any +>a : any +>b : any +} +uncheckedInternally(1,2) +>uncheckedInternally(1,2) : number +>uncheckedInternally : { (a: number, b: number): number; (a: string, b: boolean): string; } +>1 : 1 +>2 : 2 + +uncheckedInternally("zero", "one") +>uncheckedInternally("zero", "one") : never +>uncheckedInternally : { (a: number, b: number): number; (a: string, b: boolean): string; } +>"zero" : "zero" +>"one" : "one" + diff --git a/tests/cases/conformance/jsdoc/overloadTag1.ts b/tests/cases/conformance/jsdoc/overloadTag1.ts new file mode 100644 index 0000000000000..4eb66b596df44 --- /dev/null +++ b/tests/cases/conformance/jsdoc/overloadTag1.ts @@ -0,0 +1,48 @@ +// @checkJs: true +// @allowJs: true +// @outdir: foo +// @declaration: true +// @filename: overloadTag1.js +/** + * @overload + * @param {number} a + * @param {number} b + * @returns {number} + * + * @overload + * @param {string} a + * @param {boolean} b + * @returns {string} + * + * @param {string | number} a + * @param {string | number} b + * @returns {string | number} + */ +export function overloaded(a,b) { + if (typeof a === "string" && typeof b === "string") { + return a + b; + } else if (typeof a === "number" && typeof b === "number") { + return a + b; + } + throw new Error("Invalid arguments"); +} +var o1 = overloaded(1,2) +var o2 = overloaded("zero", "one") +var o3 = overloaded("a",false) + +/** + * @overload + * @param {number} a + * @param {number} b + * @returns {number} + * + * @overload + * @param {string} a + * @param {boolean} b + * @returns {string} + */ +export function uncheckedInternally(a, b) { + return a + b; +} +uncheckedInternally(1,2) +uncheckedInternally("zero", "one")