Merge pull request #25608 from Microsoft/genericRestArityCheck

Fix generic rest parameter arity checks
This commit is contained in:
Anders Hejlsberg 2018-07-12 09:09:31 -10:00 committed by GitHub
commit 0f8b6fcd27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 279 additions and 46 deletions

View File

@ -18215,7 +18215,6 @@ namespace ts {
function hasCorrectArity(node: CallLikeExpression, args: ReadonlyArray<Expression>, signature: Signature, signatureHelpTrailingComma = false) {
let argCount: number; // Apparent number of arguments we will have in this call
let typeArguments: NodeArray<TypeNode> | 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<Signature>, args: ReadonlyArray<Expression>) {
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<Signature>, typeArguments: NodeArray<TypeNode>) {
let min = Infinity;
let max = -Infinity;
@ -19008,6 +19037,7 @@ namespace ts {
// foo<number>(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<RelationComparisonResult>, 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;

View File

@ -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<TS extends unknown[]>(
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.

View File

@ -0,0 +1,15 @@
//// [genericRestArity.ts]
// Repro from #25559
declare function call<TS extends unknown[]>(
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);

View File

@ -0,0 +1,30 @@
=== tests/cases/conformance/types/rest/genericRestArity.ts ===
// Repro from #25559
declare function call<TS extends unknown[]>(
>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))

View File

@ -0,0 +1,43 @@
=== tests/cases/conformance/types/rest/genericRestArity.ts ===
// Repro from #25559
declare function call<TS extends unknown[]>(
>call : <TS extends unknown[]>(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 : <TS extends unknown[]>(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 : <TS extends unknown[]>(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

View File

@ -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<TS extends unknown[]>(
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);

View File

@ -0,0 +1,16 @@
//// [genericRestArityStrict.ts]
// Repro from #25559
declare function call<TS extends unknown[]>(
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);

View File

@ -0,0 +1,30 @@
=== tests/cases/conformance/types/rest/genericRestArityStrict.ts ===
// Repro from #25559
declare function call<TS extends unknown[]>(
>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))

View File

@ -0,0 +1,43 @@
=== tests/cases/conformance/types/rest/genericRestArityStrict.ts ===
// Repro from #25559
declare function call<TS extends unknown[]>(
>call : <TS extends unknown[]>(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 : <TS extends unknown[]>(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 : <TS extends unknown[]>(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

View File

@ -0,0 +1,8 @@
// Repro from #25559
declare function call<TS extends unknown[]>(
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);

View File

@ -0,0 +1,10 @@
// @strict: true
// Repro from #25559
declare function call<TS extends unknown[]>(
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);