Skip to content

Commit 0f8b6fc

Browse files
authored
Merge pull request microsoft#25608 from Microsoft/genericRestArityCheck
Fix generic rest parameter arity checks
2 parents af412e3 + 5822a8c commit 0f8b6fc

11 files changed

+279
-46
lines changed

src/compiler/checker.ts

Lines changed: 51 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -18215,7 +18215,6 @@ namespace ts {
1821518215

1821618216
function hasCorrectArity(node: CallLikeExpression, args: ReadonlyArray<Expression>, signature: Signature, signatureHelpTrailingComma = false) {
1821718217
let argCount: number; // Apparent number of arguments we will have in this call
18218-
let typeArguments: NodeArray<TypeNode> | undefined; // Type arguments (undefined if none)
1821918218
let callIsIncomplete = false; // In incomplete call we want to be lenient when we have too few arguments
1822018219
let spreadArgIndex = -1;
1822118220

@@ -18228,7 +18227,6 @@ namespace ts {
1822818227
// Even if the call is incomplete, we'll have a missing expression as our last argument,
1822918228
// so we can say the count is just the arg list length
1823018229
argCount = args.length;
18231-
typeArguments = node.typeArguments;
1823218230

1823318231
if (node.template.kind === SyntaxKind.TemplateExpression) {
1823418232
// If a tagged template expression lacks a tail literal, the call is incomplete.
@@ -18246,7 +18244,6 @@ namespace ts {
1824618244
}
1824718245
}
1824818246
else if (node.kind === SyntaxKind.Decorator) {
18249-
typeArguments = undefined;
1825018247
argCount = getEffectiveArgumentCount(node, /*args*/ undefined!, signature);
1825118248
}
1825218249
else {
@@ -18261,14 +18258,9 @@ namespace ts {
1826118258
// If we are missing the close parenthesis, the call is incomplete.
1826218259
callIsIncomplete = node.arguments.end === node.end;
1826318260

18264-
typeArguments = node.typeArguments;
1826518261
spreadArgIndex = getSpreadArgumentIndex(args);
1826618262
}
1826718263

18268-
if (!hasCorrectTypeArgumentArity(signature, typeArguments)) {
18269-
return false;
18270-
}
18271-
1827218264
// If a spread argument is present, check that it corresponds to a rest parameter or at least that it's in the valid range.
1827318265
if (spreadArgIndex >= 0) {
1827418266
return spreadArgIndex >= getMinArgumentCount(signature) && (hasEffectiveRestParameter(signature) || spreadArgIndex < getParameterCount(signature));
@@ -18918,6 +18910,43 @@ namespace ts {
1891818910
}
1891918911
}
1892018912

18913+
function getArgumentArityError(node: Node, signatures: ReadonlyArray<Signature>, args: ReadonlyArray<Expression>) {
18914+
let min = Number.POSITIVE_INFINITY;
18915+
let max = Number.NEGATIVE_INFINITY;
18916+
let belowArgCount = Number.NEGATIVE_INFINITY;
18917+
let aboveArgCount = Number.POSITIVE_INFINITY;
18918+
18919+
let argCount = args.length;
18920+
for (const sig of signatures) {
18921+
const minCount = getMinArgumentCount(sig);
18922+
const maxCount = getParameterCount(sig);
18923+
if (minCount < argCount && minCount > belowArgCount) belowArgCount = minCount;
18924+
if (argCount < maxCount && maxCount < aboveArgCount) aboveArgCount = maxCount;
18925+
min = Math.min(min, minCount);
18926+
max = Math.max(max, maxCount);
18927+
}
18928+
18929+
const hasRestParameter = some(signatures, hasEffectiveRestParameter);
18930+
const paramRange = hasRestParameter ? min :
18931+
min < max ? min + "-" + max :
18932+
min;
18933+
const hasSpreadArgument = getSpreadArgumentIndex(args) > -1;
18934+
if (argCount <= max && hasSpreadArgument) {
18935+
argCount--;
18936+
}
18937+
18938+
if (hasRestParameter || hasSpreadArgument) {
18939+
const error = hasRestParameter && hasSpreadArgument ? Diagnostics.Expected_at_least_0_arguments_but_got_1_or_more :
18940+
hasRestParameter ? Diagnostics.Expected_at_least_0_arguments_but_got_1 :
18941+
Diagnostics.Expected_0_arguments_but_got_1_or_more;
18942+
return createDiagnosticForNode(node, error, paramRange, argCount);
18943+
}
18944+
if (min < argCount && argCount < max) {
18945+
return createDiagnosticForNode(node, Diagnostics.No_overload_expects_0_arguments_but_overloads_do_exist_that_expect_either_1_or_2_arguments, argCount, belowArgCount, aboveArgCount);
18946+
}
18947+
return createDiagnosticForNode(node, Diagnostics.Expected_0_arguments_but_got_1, paramRange, argCount);
18948+
}
18949+
1892118950
function getTypeArgumentArityError(node: Node, signatures: ReadonlyArray<Signature>, typeArguments: NodeArray<TypeNode>) {
1892218951
let min = Infinity;
1892318952
let max = -Infinity;
@@ -19008,6 +19037,7 @@ namespace ts {
1900819037
// foo<number>(0);
1900919038
//
1901019039
let candidateForArgumentError: Signature | undefined;
19040+
let candidateForArgumentArityError: Signature | undefined;
1901119041
let candidateForTypeArgumentError: Signature | undefined;
1901219042
let result: Signature | undefined;
1901319043

@@ -19052,49 +19082,17 @@ namespace ts {
1905219082
// an error, we don't need to exclude any arguments, although it would cause no harm to do so.
1905319083
checkApplicableSignature(node, args!, candidateForArgumentError, assignableRelation, /*excludeArgument*/ undefined, /*reportErrors*/ true);
1905419084
}
19085+
else if (candidateForArgumentArityError) {
19086+
diagnostics.add(getArgumentArityError(node, [candidateForArgumentArityError], args!));
19087+
}
1905519088
else if (candidateForTypeArgumentError) {
1905619089
checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression | TaggedTemplateExpression).typeArguments!, /*reportErrors*/ true, fallbackError);
1905719090
}
1905819091
else if (typeArguments && every(signatures, sig => length(sig.typeParameters) !== typeArguments!.length)) {
1905919092
diagnostics.add(getTypeArgumentArityError(node, signatures, typeArguments));
1906019093
}
1906119094
else if (args) {
19062-
let min = Number.POSITIVE_INFINITY;
19063-
let max = Number.NEGATIVE_INFINITY;
19064-
let belowArgCount = Number.NEGATIVE_INFINITY;
19065-
let aboveArgCount = Number.POSITIVE_INFINITY;
19066-
19067-
let argCount = args.length;
19068-
for (const sig of signatures) {
19069-
const minCount = getMinArgumentCount(sig);
19070-
const maxCount = getParameterCount(sig);
19071-
if (minCount < argCount && minCount > belowArgCount) belowArgCount = minCount;
19072-
if (argCount < maxCount && maxCount < aboveArgCount) aboveArgCount = maxCount;
19073-
min = Math.min(min, minCount);
19074-
max = Math.max(max, maxCount);
19075-
}
19076-
19077-
const hasRestParameter = some(signatures, hasEffectiveRestParameter);
19078-
const paramRange = hasRestParameter ? min :
19079-
min < max ? min + "-" + max :
19080-
min;
19081-
const hasSpreadArgument = getSpreadArgumentIndex(args) > -1;
19082-
if (argCount <= max && hasSpreadArgument) {
19083-
argCount--;
19084-
}
19085-
19086-
if (hasRestParameter || hasSpreadArgument) {
19087-
const error = hasRestParameter && hasSpreadArgument ? Diagnostics.Expected_at_least_0_arguments_but_got_1_or_more :
19088-
hasRestParameter ? Diagnostics.Expected_at_least_0_arguments_but_got_1 :
19089-
Diagnostics.Expected_0_arguments_but_got_1_or_more;
19090-
diagnostics.add(createDiagnosticForNode(node, error, paramRange, argCount));
19091-
}
19092-
else if (min < argCount && argCount < max) {
19093-
diagnostics.add(createDiagnosticForNode(node, Diagnostics.No_overload_expects_0_arguments_but_overloads_do_exist_that_expect_either_1_or_2_arguments, argCount, belowArgCount, aboveArgCount));
19094-
}
19095-
else {
19096-
diagnostics.add(createDiagnosticForNode(node, Diagnostics.Expected_0_arguments_but_got_1, paramRange, argCount));
19097-
}
19095+
diagnostics.add(getArgumentArityError(node, signatures, args));
1909819096
}
1909919097
else if (fallbackError) {
1910019098
diagnostics.add(createDiagnosticForNode(node, fallbackError));
@@ -19104,11 +19102,12 @@ namespace ts {
1910419102

1910519103
function chooseOverload(candidates: Signature[], relation: Map<RelationComparisonResult>, signatureHelpTrailingComma = false) {
1910619104
candidateForArgumentError = undefined;
19105+
candidateForArgumentArityError = undefined;
1910719106
candidateForTypeArgumentError = undefined;
1910819107

1910919108
if (isSingleNonGenericCandidate) {
1911019109
const candidate = candidates[0];
19111-
if (!hasCorrectArity(node, args!, candidate, signatureHelpTrailingComma)) {
19110+
if (typeArguments || !hasCorrectArity(node, args!, candidate, signatureHelpTrailingComma)) {
1911219111
return undefined;
1911319112
}
1911419113
if (!checkApplicableSignature(node, args!, candidate, relation, excludeArgument, /*reportErrors*/ false)) {
@@ -19120,7 +19119,7 @@ namespace ts {
1912019119

1912119120
for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) {
1912219121
const originalCandidate = candidates[candidateIndex];
19123-
if (!hasCorrectArity(node, args!, originalCandidate, signatureHelpTrailingComma)) {
19122+
if (!hasCorrectTypeArgumentArity(originalCandidate, typeArguments) || !hasCorrectArity(node, args!, originalCandidate, signatureHelpTrailingComma)) {
1912419123
continue;
1912519124
}
1912619125

@@ -19148,6 +19147,12 @@ namespace ts {
1914819147
}
1914919148
const isJavascript = isInJavaScriptFile(candidate.declaration);
1915019149
candidate = getSignatureInstantiation(candidate, typeArgumentTypes, isJavascript);
19150+
// If the original signature has a rest type parameter, instantiation may produce a
19151+
// signature with different arity and we need to perform another arity check.
19152+
if (getRestTypeParameter(originalCandidate) && !hasCorrectArity(node, args!, candidate, signatureHelpTrailingComma)) {
19153+
candidateForArgumentArityError = candidate;
19154+
break;
19155+
}
1915119156
}
1915219157
if (!checkApplicableSignature(node, args!, candidate, relation, excludeArgument, /*reportErrors*/ false)) {
1915319158
candidateForArgumentError = candidate;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
tests/cases/conformance/types/rest/genericRestArity.ts(7,1): error TS2554: Expected 3 arguments, but got 1.
2+
tests/cases/conformance/types/rest/genericRestArity.ts(8,1): error TS2554: Expected 3 arguments, but got 8.
3+
4+
5+
==== tests/cases/conformance/types/rest/genericRestArity.ts (2 errors) ====
6+
// Repro from #25559
7+
8+
declare function call<TS extends unknown[]>(
9+
handler: (...args: TS) => void,
10+
...args: TS): void;
11+
12+
call((x: number, y: number) => x + y);
13+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
14+
!!! error TS2554: Expected 3 arguments, but got 1.
15+
call((x: number, y: number) => x + y, 1, 2, 3, 4, 5, 6, 7);
16+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17+
!!! error TS2554: Expected 3 arguments, but got 8.
18+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//// [genericRestArity.ts]
2+
// Repro from #25559
3+
4+
declare function call<TS extends unknown[]>(
5+
handler: (...args: TS) => void,
6+
...args: TS): void;
7+
8+
call((x: number, y: number) => x + y);
9+
call((x: number, y: number) => x + y, 1, 2, 3, 4, 5, 6, 7);
10+
11+
12+
//// [genericRestArity.js]
13+
// Repro from #25559
14+
call(function (x, y) { return x + y; });
15+
call(function (x, y) { return x + y; }, 1, 2, 3, 4, 5, 6, 7);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
=== tests/cases/conformance/types/rest/genericRestArity.ts ===
2+
// Repro from #25559
3+
4+
declare function call<TS extends unknown[]>(
5+
>call : Symbol(call, Decl(genericRestArity.ts, 0, 0))
6+
>TS : Symbol(TS, Decl(genericRestArity.ts, 2, 22))
7+
8+
handler: (...args: TS) => void,
9+
>handler : Symbol(handler, Decl(genericRestArity.ts, 2, 44))
10+
>args : Symbol(args, Decl(genericRestArity.ts, 3, 14))
11+
>TS : Symbol(TS, Decl(genericRestArity.ts, 2, 22))
12+
13+
...args: TS): void;
14+
>args : Symbol(args, Decl(genericRestArity.ts, 3, 35))
15+
>TS : Symbol(TS, Decl(genericRestArity.ts, 2, 22))
16+
17+
call((x: number, y: number) => x + y);
18+
>call : Symbol(call, Decl(genericRestArity.ts, 0, 0))
19+
>x : Symbol(x, Decl(genericRestArity.ts, 6, 6))
20+
>y : Symbol(y, Decl(genericRestArity.ts, 6, 16))
21+
>x : Symbol(x, Decl(genericRestArity.ts, 6, 6))
22+
>y : Symbol(y, Decl(genericRestArity.ts, 6, 16))
23+
24+
call((x: number, y: number) => x + y, 1, 2, 3, 4, 5, 6, 7);
25+
>call : Symbol(call, Decl(genericRestArity.ts, 0, 0))
26+
>x : Symbol(x, Decl(genericRestArity.ts, 7, 6))
27+
>y : Symbol(y, Decl(genericRestArity.ts, 7, 16))
28+
>x : Symbol(x, Decl(genericRestArity.ts, 7, 6))
29+
>y : Symbol(y, Decl(genericRestArity.ts, 7, 16))
30+
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
=== tests/cases/conformance/types/rest/genericRestArity.ts ===
2+
// Repro from #25559
3+
4+
declare function call<TS extends unknown[]>(
5+
>call : <TS extends unknown[]>(handler: (...args: TS) => void, ...args: TS) => void
6+
>TS : TS
7+
8+
handler: (...args: TS) => void,
9+
>handler : (...args: TS) => void
10+
>args : TS
11+
>TS : TS
12+
13+
...args: TS): void;
14+
>args : TS
15+
>TS : TS
16+
17+
call((x: number, y: number) => x + y);
18+
>call((x: number, y: number) => x + y) : any
19+
>call : <TS extends unknown[]>(handler: (...args: TS) => void, ...args: TS) => void
20+
>(x: number, y: number) => x + y : (x: number, y: number) => number
21+
>x : number
22+
>y : number
23+
>x + y : number
24+
>x : number
25+
>y : number
26+
27+
call((x: number, y: number) => x + y, 1, 2, 3, 4, 5, 6, 7);
28+
>call((x: number, y: number) => x + y, 1, 2, 3, 4, 5, 6, 7) : any
29+
>call : <TS extends unknown[]>(handler: (...args: TS) => void, ...args: TS) => void
30+
>(x: number, y: number) => x + y : (x: number, y: number) => number
31+
>x : number
32+
>y : number
33+
>x + y : number
34+
>x : number
35+
>y : number
36+
>1 : 1
37+
>2 : 2
38+
>3 : 3
39+
>4 : 4
40+
>5 : 5
41+
>6 : 6
42+
>7 : 7
43+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
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'.
2+
3+
4+
==== tests/cases/conformance/types/rest/genericRestArityStrict.ts (1 errors) ====
5+
// Repro from #25559
6+
7+
declare function call<TS extends unknown[]>(
8+
handler: (...args: TS) => void,
9+
...args: TS): void;
10+
11+
call((x: number, y: number) => x + y);
12+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
13+
!!! error TS2345: Argument of type '(x: number, y: number) => number' is not assignable to parameter of type '() => void'.
14+
call((x: number, y: number) => x + y, 1, 2, 3, 4, 5, 6, 7);
15+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//// [genericRestArityStrict.ts]
2+
// Repro from #25559
3+
4+
declare function call<TS extends unknown[]>(
5+
handler: (...args: TS) => void,
6+
...args: TS): void;
7+
8+
call((x: number, y: number) => x + y);
9+
call((x: number, y: number) => x + y, 1, 2, 3, 4, 5, 6, 7);
10+
11+
12+
//// [genericRestArityStrict.js]
13+
"use strict";
14+
// Repro from #25559
15+
call(function (x, y) { return x + y; });
16+
call(function (x, y) { return x + y; }, 1, 2, 3, 4, 5, 6, 7);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
=== tests/cases/conformance/types/rest/genericRestArityStrict.ts ===
2+
// Repro from #25559
3+
4+
declare function call<TS extends unknown[]>(
5+
>call : Symbol(call, Decl(genericRestArityStrict.ts, 0, 0))
6+
>TS : Symbol(TS, Decl(genericRestArityStrict.ts, 2, 22))
7+
8+
handler: (...args: TS) => void,
9+
>handler : Symbol(handler, Decl(genericRestArityStrict.ts, 2, 44))
10+
>args : Symbol(args, Decl(genericRestArityStrict.ts, 3, 14))
11+
>TS : Symbol(TS, Decl(genericRestArityStrict.ts, 2, 22))
12+
13+
...args: TS): void;
14+
>args : Symbol(args, Decl(genericRestArityStrict.ts, 3, 35))
15+
>TS : Symbol(TS, Decl(genericRestArityStrict.ts, 2, 22))
16+
17+
call((x: number, y: number) => x + y);
18+
>call : Symbol(call, Decl(genericRestArityStrict.ts, 0, 0))
19+
>x : Symbol(x, Decl(genericRestArityStrict.ts, 6, 6))
20+
>y : Symbol(y, Decl(genericRestArityStrict.ts, 6, 16))
21+
>x : Symbol(x, Decl(genericRestArityStrict.ts, 6, 6))
22+
>y : Symbol(y, Decl(genericRestArityStrict.ts, 6, 16))
23+
24+
call((x: number, y: number) => x + y, 1, 2, 3, 4, 5, 6, 7);
25+
>call : Symbol(call, Decl(genericRestArityStrict.ts, 0, 0))
26+
>x : Symbol(x, Decl(genericRestArityStrict.ts, 7, 6))
27+
>y : Symbol(y, Decl(genericRestArityStrict.ts, 7, 16))
28+
>x : Symbol(x, Decl(genericRestArityStrict.ts, 7, 6))
29+
>y : Symbol(y, Decl(genericRestArityStrict.ts, 7, 16))
30+

0 commit comments

Comments
 (0)