diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 35173231e50..45fc76efeab 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18215,7 +18215,6 @@ namespace ts { function hasCorrectArity(node: CallLikeExpression, args: ReadonlyArray, signature: Signature, signatureHelpTrailingComma = false) { let argCount: number; // Apparent number of arguments we will have in this call - let typeArguments: NodeArray | undefined; // Type arguments (undefined if none) let callIsIncomplete = false; // In incomplete call we want to be lenient when we have too few arguments let spreadArgIndex = -1; @@ -18228,7 +18227,6 @@ namespace ts { // Even if the call is incomplete, we'll have a missing expression as our last argument, // so we can say the count is just the arg list length argCount = args.length; - typeArguments = node.typeArguments; if (node.template.kind === SyntaxKind.TemplateExpression) { // If a tagged template expression lacks a tail literal, the call is incomplete. @@ -18246,7 +18244,6 @@ namespace ts { } } else if (node.kind === SyntaxKind.Decorator) { - typeArguments = undefined; argCount = getEffectiveArgumentCount(node, /*args*/ undefined!, signature); } else { @@ -18261,14 +18258,9 @@ namespace ts { // If we are missing the close parenthesis, the call is incomplete. callIsIncomplete = node.arguments.end === node.end; - typeArguments = node.typeArguments; spreadArgIndex = getSpreadArgumentIndex(args); } - if (!hasCorrectTypeArgumentArity(signature, typeArguments)) { - return false; - } - // If a spread argument is present, check that it corresponds to a rest parameter or at least that it's in the valid range. if (spreadArgIndex >= 0) { return spreadArgIndex >= getMinArgumentCount(signature) && (hasEffectiveRestParameter(signature) || spreadArgIndex < getParameterCount(signature)); @@ -18918,6 +18910,43 @@ namespace ts { } } + function getArgumentArityError(node: Node, signatures: ReadonlyArray, args: ReadonlyArray) { + let min = Number.POSITIVE_INFINITY; + let max = Number.NEGATIVE_INFINITY; + let belowArgCount = Number.NEGATIVE_INFINITY; + let aboveArgCount = Number.POSITIVE_INFINITY; + + let argCount = args.length; + for (const sig of signatures) { + const minCount = getMinArgumentCount(sig); + const maxCount = getParameterCount(sig); + if (minCount < argCount && minCount > belowArgCount) belowArgCount = minCount; + if (argCount < maxCount && maxCount < aboveArgCount) aboveArgCount = maxCount; + min = Math.min(min, minCount); + max = Math.max(max, maxCount); + } + + const hasRestParameter = some(signatures, hasEffectiveRestParameter); + const paramRange = hasRestParameter ? min : + min < max ? min + "-" + max : + min; + const hasSpreadArgument = getSpreadArgumentIndex(args) > -1; + if (argCount <= max && hasSpreadArgument) { + argCount--; + } + + if (hasRestParameter || hasSpreadArgument) { + const error = hasRestParameter && hasSpreadArgument ? Diagnostics.Expected_at_least_0_arguments_but_got_1_or_more : + hasRestParameter ? Diagnostics.Expected_at_least_0_arguments_but_got_1 : + Diagnostics.Expected_0_arguments_but_got_1_or_more; + return createDiagnosticForNode(node, error, paramRange, argCount); + } + if (min < argCount && argCount < max) { + return createDiagnosticForNode(node, Diagnostics.No_overload_expects_0_arguments_but_overloads_do_exist_that_expect_either_1_or_2_arguments, argCount, belowArgCount, aboveArgCount); + } + return createDiagnosticForNode(node, Diagnostics.Expected_0_arguments_but_got_1, paramRange, argCount); + } + function getTypeArgumentArityError(node: Node, signatures: ReadonlyArray, typeArguments: NodeArray) { let min = Infinity; let max = -Infinity; @@ -19008,6 +19037,7 @@ namespace ts { // foo(0); // let candidateForArgumentError: Signature | undefined; + let candidateForArgumentArityError: Signature | undefined; let candidateForTypeArgumentError: Signature | undefined; let result: Signature | undefined; @@ -19052,6 +19082,9 @@ namespace ts { // an error, we don't need to exclude any arguments, although it would cause no harm to do so. checkApplicableSignature(node, args!, candidateForArgumentError, assignableRelation, /*excludeArgument*/ undefined, /*reportErrors*/ true); } + else if (candidateForArgumentArityError) { + diagnostics.add(getArgumentArityError(node, [candidateForArgumentArityError], args!)); + } else if (candidateForTypeArgumentError) { checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression | TaggedTemplateExpression).typeArguments!, /*reportErrors*/ true, fallbackError); } @@ -19059,42 +19092,7 @@ namespace ts { diagnostics.add(getTypeArgumentArityError(node, signatures, typeArguments)); } else if (args) { - let min = Number.POSITIVE_INFINITY; - let max = Number.NEGATIVE_INFINITY; - let belowArgCount = Number.NEGATIVE_INFINITY; - let aboveArgCount = Number.POSITIVE_INFINITY; - - let argCount = args.length; - for (const sig of signatures) { - const minCount = getMinArgumentCount(sig); - const maxCount = getParameterCount(sig); - if (minCount < argCount && minCount > belowArgCount) belowArgCount = minCount; - if (argCount < maxCount && maxCount < aboveArgCount) aboveArgCount = maxCount; - min = Math.min(min, minCount); - max = Math.max(max, maxCount); - } - - const hasRestParameter = some(signatures, hasEffectiveRestParameter); - const paramRange = hasRestParameter ? min : - min < max ? min + "-" + max : - min; - const hasSpreadArgument = getSpreadArgumentIndex(args) > -1; - if (argCount <= max && hasSpreadArgument) { - argCount--; - } - - if (hasRestParameter || hasSpreadArgument) { - const error = hasRestParameter && hasSpreadArgument ? Diagnostics.Expected_at_least_0_arguments_but_got_1_or_more : - hasRestParameter ? Diagnostics.Expected_at_least_0_arguments_but_got_1 : - Diagnostics.Expected_0_arguments_but_got_1_or_more; - diagnostics.add(createDiagnosticForNode(node, error, paramRange, argCount)); - } - else if (min < argCount && argCount < max) { - diagnostics.add(createDiagnosticForNode(node, Diagnostics.No_overload_expects_0_arguments_but_overloads_do_exist_that_expect_either_1_or_2_arguments, argCount, belowArgCount, aboveArgCount)); - } - else { - diagnostics.add(createDiagnosticForNode(node, Diagnostics.Expected_0_arguments_but_got_1, paramRange, argCount)); - } + diagnostics.add(getArgumentArityError(node, signatures, args)); } else if (fallbackError) { diagnostics.add(createDiagnosticForNode(node, fallbackError)); @@ -19104,11 +19102,12 @@ namespace ts { function chooseOverload(candidates: Signature[], relation: Map, signatureHelpTrailingComma = false) { candidateForArgumentError = undefined; + candidateForArgumentArityError = undefined; candidateForTypeArgumentError = undefined; if (isSingleNonGenericCandidate) { const candidate = candidates[0]; - if (!hasCorrectArity(node, args!, candidate, signatureHelpTrailingComma)) { + if (typeArguments || !hasCorrectArity(node, args!, candidate, signatureHelpTrailingComma)) { return undefined; } if (!checkApplicableSignature(node, args!, candidate, relation, excludeArgument, /*reportErrors*/ false)) { @@ -19120,7 +19119,7 @@ namespace ts { for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) { const originalCandidate = candidates[candidateIndex]; - if (!hasCorrectArity(node, args!, originalCandidate, signatureHelpTrailingComma)) { + if (!hasCorrectTypeArgumentArity(originalCandidate, typeArguments) || !hasCorrectArity(node, args!, originalCandidate, signatureHelpTrailingComma)) { continue; } @@ -19148,6 +19147,12 @@ namespace ts { } const isJavascript = isInJavaScriptFile(candidate.declaration); candidate = getSignatureInstantiation(candidate, typeArgumentTypes, isJavascript); + // If the original signature has a rest type parameter, instantiation may produce a + // signature with different arity and we need to perform another arity check. + if (getRestTypeParameter(originalCandidate) && !hasCorrectArity(node, args!, candidate, signatureHelpTrailingComma)) { + candidateForArgumentArityError = candidate; + break; + } } if (!checkApplicableSignature(node, args!, candidate, relation, excludeArgument, /*reportErrors*/ false)) { candidateForArgumentError = candidate; diff --git a/tests/baselines/reference/genericRestArity.errors.txt b/tests/baselines/reference/genericRestArity.errors.txt new file mode 100644 index 00000000000..71b814e324f --- /dev/null +++ b/tests/baselines/reference/genericRestArity.errors.txt @@ -0,0 +1,18 @@ +tests/cases/conformance/types/rest/genericRestArity.ts(7,1): error TS2554: Expected 3 arguments, but got 1. +tests/cases/conformance/types/rest/genericRestArity.ts(8,1): error TS2554: Expected 3 arguments, but got 8. + + +==== tests/cases/conformance/types/rest/genericRestArity.ts (2 errors) ==== + // Repro from #25559 + + declare function call( + handler: (...args: TS) => void, + ...args: TS): void; + + call((x: number, y: number) => x + y); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2554: Expected 3 arguments, but got 1. + call((x: number, y: number) => x + y, 1, 2, 3, 4, 5, 6, 7); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2554: Expected 3 arguments, but got 8. + \ No newline at end of file diff --git a/tests/baselines/reference/genericRestArity.js b/tests/baselines/reference/genericRestArity.js new file mode 100644 index 00000000000..a59ab66e1a8 --- /dev/null +++ b/tests/baselines/reference/genericRestArity.js @@ -0,0 +1,15 @@ +//// [genericRestArity.ts] +// Repro from #25559 + +declare function call( + handler: (...args: TS) => void, + ...args: TS): void; + +call((x: number, y: number) => x + y); +call((x: number, y: number) => x + y, 1, 2, 3, 4, 5, 6, 7); + + +//// [genericRestArity.js] +// Repro from #25559 +call(function (x, y) { return x + y; }); +call(function (x, y) { return x + y; }, 1, 2, 3, 4, 5, 6, 7); diff --git a/tests/baselines/reference/genericRestArity.symbols b/tests/baselines/reference/genericRestArity.symbols new file mode 100644 index 00000000000..65292356029 --- /dev/null +++ b/tests/baselines/reference/genericRestArity.symbols @@ -0,0 +1,30 @@ +=== tests/cases/conformance/types/rest/genericRestArity.ts === +// Repro from #25559 + +declare function call( +>call : Symbol(call, Decl(genericRestArity.ts, 0, 0)) +>TS : Symbol(TS, Decl(genericRestArity.ts, 2, 22)) + + handler: (...args: TS) => void, +>handler : Symbol(handler, Decl(genericRestArity.ts, 2, 44)) +>args : Symbol(args, Decl(genericRestArity.ts, 3, 14)) +>TS : Symbol(TS, Decl(genericRestArity.ts, 2, 22)) + + ...args: TS): void; +>args : Symbol(args, Decl(genericRestArity.ts, 3, 35)) +>TS : Symbol(TS, Decl(genericRestArity.ts, 2, 22)) + +call((x: number, y: number) => x + y); +>call : Symbol(call, Decl(genericRestArity.ts, 0, 0)) +>x : Symbol(x, Decl(genericRestArity.ts, 6, 6)) +>y : Symbol(y, Decl(genericRestArity.ts, 6, 16)) +>x : Symbol(x, Decl(genericRestArity.ts, 6, 6)) +>y : Symbol(y, Decl(genericRestArity.ts, 6, 16)) + +call((x: number, y: number) => x + y, 1, 2, 3, 4, 5, 6, 7); +>call : Symbol(call, Decl(genericRestArity.ts, 0, 0)) +>x : Symbol(x, Decl(genericRestArity.ts, 7, 6)) +>y : Symbol(y, Decl(genericRestArity.ts, 7, 16)) +>x : Symbol(x, Decl(genericRestArity.ts, 7, 6)) +>y : Symbol(y, Decl(genericRestArity.ts, 7, 16)) + diff --git a/tests/baselines/reference/genericRestArity.types b/tests/baselines/reference/genericRestArity.types new file mode 100644 index 00000000000..61bd75d9470 --- /dev/null +++ b/tests/baselines/reference/genericRestArity.types @@ -0,0 +1,43 @@ +=== tests/cases/conformance/types/rest/genericRestArity.ts === +// Repro from #25559 + +declare function call( +>call : (handler: (...args: TS) => void, ...args: TS) => void +>TS : TS + + handler: (...args: TS) => void, +>handler : (...args: TS) => void +>args : TS +>TS : TS + + ...args: TS): void; +>args : TS +>TS : TS + +call((x: number, y: number) => x + y); +>call((x: number, y: number) => x + y) : any +>call : (handler: (...args: TS) => void, ...args: TS) => void +>(x: number, y: number) => x + y : (x: number, y: number) => number +>x : number +>y : number +>x + y : number +>x : number +>y : number + +call((x: number, y: number) => x + y, 1, 2, 3, 4, 5, 6, 7); +>call((x: number, y: number) => x + y, 1, 2, 3, 4, 5, 6, 7) : any +>call : (handler: (...args: TS) => void, ...args: TS) => void +>(x: number, y: number) => x + y : (x: number, y: number) => number +>x : number +>y : number +>x + y : number +>x : number +>y : number +>1 : 1 +>2 : 2 +>3 : 3 +>4 : 4 +>5 : 5 +>6 : 6 +>7 : 7 + diff --git a/tests/baselines/reference/genericRestArityStrict.errors.txt b/tests/baselines/reference/genericRestArityStrict.errors.txt new file mode 100644 index 00000000000..9a77f8d8498 --- /dev/null +++ b/tests/baselines/reference/genericRestArityStrict.errors.txt @@ -0,0 +1,15 @@ +tests/cases/conformance/types/rest/genericRestArityStrict.ts(7,6): error TS2345: Argument of type '(x: number, y: number) => number' is not assignable to parameter of type '() => void'. + + +==== tests/cases/conformance/types/rest/genericRestArityStrict.ts (1 errors) ==== + // Repro from #25559 + + declare function call( + handler: (...args: TS) => void, + ...args: TS): void; + + call((x: number, y: number) => x + y); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2345: Argument of type '(x: number, y: number) => number' is not assignable to parameter of type '() => void'. + call((x: number, y: number) => x + y, 1, 2, 3, 4, 5, 6, 7); + \ No newline at end of file diff --git a/tests/baselines/reference/genericRestArityStrict.js b/tests/baselines/reference/genericRestArityStrict.js new file mode 100644 index 00000000000..5628eada2f5 --- /dev/null +++ b/tests/baselines/reference/genericRestArityStrict.js @@ -0,0 +1,16 @@ +//// [genericRestArityStrict.ts] +// Repro from #25559 + +declare function call( + handler: (...args: TS) => void, + ...args: TS): void; + +call((x: number, y: number) => x + y); +call((x: number, y: number) => x + y, 1, 2, 3, 4, 5, 6, 7); + + +//// [genericRestArityStrict.js] +"use strict"; +// Repro from #25559 +call(function (x, y) { return x + y; }); +call(function (x, y) { return x + y; }, 1, 2, 3, 4, 5, 6, 7); diff --git a/tests/baselines/reference/genericRestArityStrict.symbols b/tests/baselines/reference/genericRestArityStrict.symbols new file mode 100644 index 00000000000..df51a85f95d --- /dev/null +++ b/tests/baselines/reference/genericRestArityStrict.symbols @@ -0,0 +1,30 @@ +=== tests/cases/conformance/types/rest/genericRestArityStrict.ts === +// Repro from #25559 + +declare function call( +>call : Symbol(call, Decl(genericRestArityStrict.ts, 0, 0)) +>TS : Symbol(TS, Decl(genericRestArityStrict.ts, 2, 22)) + + handler: (...args: TS) => void, +>handler : Symbol(handler, Decl(genericRestArityStrict.ts, 2, 44)) +>args : Symbol(args, Decl(genericRestArityStrict.ts, 3, 14)) +>TS : Symbol(TS, Decl(genericRestArityStrict.ts, 2, 22)) + + ...args: TS): void; +>args : Symbol(args, Decl(genericRestArityStrict.ts, 3, 35)) +>TS : Symbol(TS, Decl(genericRestArityStrict.ts, 2, 22)) + +call((x: number, y: number) => x + y); +>call : Symbol(call, Decl(genericRestArityStrict.ts, 0, 0)) +>x : Symbol(x, Decl(genericRestArityStrict.ts, 6, 6)) +>y : Symbol(y, Decl(genericRestArityStrict.ts, 6, 16)) +>x : Symbol(x, Decl(genericRestArityStrict.ts, 6, 6)) +>y : Symbol(y, Decl(genericRestArityStrict.ts, 6, 16)) + +call((x: number, y: number) => x + y, 1, 2, 3, 4, 5, 6, 7); +>call : Symbol(call, Decl(genericRestArityStrict.ts, 0, 0)) +>x : Symbol(x, Decl(genericRestArityStrict.ts, 7, 6)) +>y : Symbol(y, Decl(genericRestArityStrict.ts, 7, 16)) +>x : Symbol(x, Decl(genericRestArityStrict.ts, 7, 6)) +>y : Symbol(y, Decl(genericRestArityStrict.ts, 7, 16)) + diff --git a/tests/baselines/reference/genericRestArityStrict.types b/tests/baselines/reference/genericRestArityStrict.types new file mode 100644 index 00000000000..53c784d0938 --- /dev/null +++ b/tests/baselines/reference/genericRestArityStrict.types @@ -0,0 +1,43 @@ +=== tests/cases/conformance/types/rest/genericRestArityStrict.ts === +// Repro from #25559 + +declare function call( +>call : (handler: (...args: TS) => void, ...args: TS) => void +>TS : TS + + handler: (...args: TS) => void, +>handler : (...args: TS) => void +>args : TS +>TS : TS + + ...args: TS): void; +>args : TS +>TS : TS + +call((x: number, y: number) => x + y); +>call((x: number, y: number) => x + y) : any +>call : (handler: (...args: TS) => void, ...args: TS) => void +>(x: number, y: number) => x + y : (x: number, y: number) => number +>x : number +>y : number +>x + y : number +>x : number +>y : number + +call((x: number, y: number) => x + y, 1, 2, 3, 4, 5, 6, 7); +>call((x: number, y: number) => x + y, 1, 2, 3, 4, 5, 6, 7) : void +>call : (handler: (...args: TS) => void, ...args: TS) => void +>(x: number, y: number) => x + y : (x: number, y: number) => number +>x : number +>y : number +>x + y : number +>x : number +>y : number +>1 : 1 +>2 : 2 +>3 : 3 +>4 : 4 +>5 : 5 +>6 : 6 +>7 : 7 + diff --git a/tests/cases/conformance/types/rest/genericRestArity.ts b/tests/cases/conformance/types/rest/genericRestArity.ts new file mode 100644 index 00000000000..c417b271b3b --- /dev/null +++ b/tests/cases/conformance/types/rest/genericRestArity.ts @@ -0,0 +1,8 @@ +// Repro from #25559 + +declare function call( + handler: (...args: TS) => void, + ...args: TS): void; + +call((x: number, y: number) => x + y); +call((x: number, y: number) => x + y, 1, 2, 3, 4, 5, 6, 7); diff --git a/tests/cases/conformance/types/rest/genericRestArityStrict.ts b/tests/cases/conformance/types/rest/genericRestArityStrict.ts new file mode 100644 index 00000000000..673479fea59 --- /dev/null +++ b/tests/cases/conformance/types/rest/genericRestArityStrict.ts @@ -0,0 +1,10 @@ +// @strict: true + +// Repro from #25559 + +declare function call( + handler: (...args: TS) => void, + ...args: TS): void; + +call((x: number, y: number) => x + y); +call((x: number, y: number) => x + y, 1, 2, 3, 4, 5, 6, 7);