Check overload tag against implementation (#52474)

This commit is contained in:
Nathan Shively-Sanders
2023-02-16 14:40:40 -08:00
committed by GitHub
parent 2976b6b703
commit 4b534dc859
6 changed files with 429 additions and 1 deletions

View File

@@ -748,6 +748,7 @@ import {
JSDocMemberName,
JSDocNullableType,
JSDocOptionalType,
JSDocOverloadTag,
JSDocParameterTag,
JSDocPrivateTag,
JSDocPropertyLikeTag,
@@ -38692,6 +38693,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;
}
}
}
}
}
}
}
@@ -38743,8 +38755,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;

View File

@@ -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'.

View File

@@ -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;

View File

@@ -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))

View File

@@ -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"

View File

@@ -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")