Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #4260 : Allow trailing arguments that accept void to be omitted #27522

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18805,6 +18805,10 @@ namespace ts {
return findIndex(args, isSpreadArgument);
}

function acceptsVoid(t: Type): boolean {
return !!(t.flags & TypeFlags.Void);
}

function hasCorrectArity(node: CallLikeExpression, args: ReadonlyArray<Expression>, signature: Signature, signatureHelpTrailingComma = false) {
let argCount: number;
let callIsIncomplete = false; // In incomplete call we want to be lenient when we have too few arguments
Expand Down Expand Up @@ -18866,8 +18870,16 @@ namespace ts {

// If the call is incomplete, we should skip the lower bound check.
// JSX signatures can have extra parameters provided by the library which we don't check
const hasEnoughArguments = argCount >= effectiveMinimumArguments;
return callIsIncomplete || hasEnoughArguments;
if (callIsIncomplete || argCount >= effectiveMinimumArguments) {
return true;
}
for (let i = argCount; i < effectiveMinimumArguments; i++) {
const type = getTypeAtPosition(signature, i);
if (filterType(type, acceptsVoid).flags & TypeFlags.Never) {
return false;
}
}
return true;
}

function hasCorrectTypeArgumentArity(signature: Signature, typeArguments: NodeArray<TypeNode> | undefined) {
Expand Down
131 changes: 131 additions & 0 deletions tests/baselines/reference/callWithMissingVoid.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts(16,1): error TS2554: Expected 1 arguments, but got 0.
tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts(19,1): error TS2554: Expected 1 arguments, but got 0.
tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts(22,1): error TS2554: Expected 1 arguments, but got 0.
tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts(35,31): error TS2554: Expected 1 arguments, but got 0.
tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts(36,35): error TS2554: Expected 1 arguments, but got 0.
tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts(37,33): error TS2554: Expected 1 arguments, but got 0.
tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts(48,1): error TS2554: Expected 3 arguments, but got 1.
tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts(55,1): error TS2554: Expected 4 arguments, but got 2.
tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts(56,1): error TS2554: Expected 4 arguments, but got 3.
tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts(57,1): error TS2554: Expected 4 arguments, but got 1.
tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts(75,1): error TS2554: Expected 3 arguments, but got 1.


==== tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts (11 errors) ====
// From #4260
class X<T> {
f(t: T) {
return { a: t };
}
}

declare const x: X<void>;
x.f() // no error because f expects void

declare const xUnion: X<void | number>;
xUnion.f(42) // no error because f accepts number
xUnion.f() // no error because f accepts void

declare const xAny: X<any>;
xAny.f() // error, any still expects an argument
~~~~~~~~
!!! error TS2554: Expected 1 arguments, but got 0.
!!! related TS6210 tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts:3:7: An argument for 't' was not provided.

declare const xUnknown: X<unknown>;
xUnknown.f() // error, unknown still expects an argument
~~~~~~~~~~~~
!!! error TS2554: Expected 1 arguments, but got 0.
!!! related TS6210 tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts:3:7: An argument for 't' was not provided.

declare const xNever: X<never>;
xNever.f() // error, never still expects an argument
~~~~~~~~~~
!!! error TS2554: Expected 1 arguments, but got 0.
!!! related TS6210 tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts:3:7: An argument for 't' was not provided.


// Promise has previously been updated to work without arguments, but to show this fixes the issue too.

class MyPromise<X> {
constructor(executor: (resolve: (value: X) => void) => void) {

}
}

new MyPromise<void>(resolve => resolve()); // no error
new MyPromise<void | number>(resolve => resolve()); // no error
new MyPromise<any>(resolve => resolve()); // error, `any` arguments cannot be omitted
~~~~~~~~~
!!! error TS2554: Expected 1 arguments, but got 0.
!!! related TS6210 tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts:28:38: An argument for 'value' was not provided.
new MyPromise<unknown>(resolve => resolve()); // error, `unknown` arguments cannot be omitted
~~~~~~~~~
!!! error TS2554: Expected 1 arguments, but got 0.
!!! related TS6210 tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts:28:38: An argument for 'value' was not provided.
new MyPromise<never>(resolve => resolve()); // error, `never` arguments cannot be omitted
~~~~~~~~~
!!! error TS2554: Expected 1 arguments, but got 0.
!!! related TS6210 tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts:28:38: An argument for 'value' was not provided.


// Multiple parameters

function a(x: number, y: string, z: void): void {

}

a(4, "hello"); // ok
a(4, "hello", void 0); // ok
a(4); // not ok
~~~~
!!! error TS2554: Expected 3 arguments, but got 1.
!!! related TS6210 tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts:42:23: An argument for 'y' was not provided.

function b(x: number, y: string, z: void, what: number): void {

}

b(4, "hello", void 0, 2); // ok
b(4, "hello"); // not ok
~~~~~~~~~~~~~
!!! error TS2554: Expected 4 arguments, but got 2.
!!! related TS6210 tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts:50:34: An argument for 'z' was not provided.
b(4, "hello", void 0); // not ok
~~~~~~~~~~~~~~~~~~~~~
!!! error TS2554: Expected 4 arguments, but got 3.
!!! related TS6210 tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts:50:43: An argument for 'what' was not provided.
b(4); // not ok
~~~~
!!! error TS2554: Expected 4 arguments, but got 1.
!!! related TS6210 tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts:50:23: An argument for 'y' was not provided.

function c(x: number | void, y: void, z: void | string | number): void {

}

c(3, void 0, void 0); // ok
c(3, void 0); // ok
c(3); // ok
c(); // ok


// Spread Parameters

declare function call<TS extends unknown[]>(
handler: (...args: TS) => unknown,
...args: TS): void;

call((x: number, y: number) => x + y) // error
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2554: Expected 3 arguments, but got 1.
!!! related TS6210 tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts:73:5: An argument for 'args' was not provided.
call((x: number, y: number) => x + y, 4, 2) // ok

call((x: number, y: void) => x, 4, void 0) // ok
call((x: number, y: void) => x, 4) // ok
call((x: void, y: void) => 42) // ok
call((x: number | void, y: number | void) => 42) // ok
call((x: number | void, y: number | void) => 42, 4) // ok
call((x: number | void, y: number | void) => 42, 4, 2) // ok

140 changes: 140 additions & 0 deletions tests/baselines/reference/callWithMissingVoid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
//// [callWithMissingVoid.ts]
// From #4260
class X<T> {
f(t: T) {
return { a: t };
}
}

declare const x: X<void>;
x.f() // no error because f expects void

declare const xUnion: X<void | number>;
xUnion.f(42) // no error because f accepts number
xUnion.f() // no error because f accepts void

declare const xAny: X<any>;
xAny.f() // error, any still expects an argument

declare const xUnknown: X<unknown>;
xUnknown.f() // error, unknown still expects an argument

declare const xNever: X<never>;
xNever.f() // error, never still expects an argument


// Promise has previously been updated to work without arguments, but to show this fixes the issue too.

class MyPromise<X> {
constructor(executor: (resolve: (value: X) => void) => void) {

}
}

new MyPromise<void>(resolve => resolve()); // no error
new MyPromise<void | number>(resolve => resolve()); // no error
new MyPromise<any>(resolve => resolve()); // error, `any` arguments cannot be omitted
new MyPromise<unknown>(resolve => resolve()); // error, `unknown` arguments cannot be omitted
new MyPromise<never>(resolve => resolve()); // error, `never` arguments cannot be omitted


// Multiple parameters

function a(x: number, y: string, z: void): void {

}

a(4, "hello"); // ok
a(4, "hello", void 0); // ok
a(4); // not ok

function b(x: number, y: string, z: void, what: number): void {

}

b(4, "hello", void 0, 2); // ok
b(4, "hello"); // not ok
b(4, "hello", void 0); // not ok
b(4); // not ok

function c(x: number | void, y: void, z: void | string | number): void {

}

c(3, void 0, void 0); // ok
c(3, void 0); // ok
c(3); // ok
c(); // ok


// Spread Parameters

declare function call<TS extends unknown[]>(
handler: (...args: TS) => unknown,
...args: TS): void;

call((x: number, y: number) => x + y) // error
call((x: number, y: number) => x + y, 4, 2) // ok

call((x: number, y: void) => x, 4, void 0) // ok
call((x: number, y: void) => x, 4) // ok
call((x: void, y: void) => 42) // ok
call((x: number | void, y: number | void) => 42) // ok
call((x: number | void, y: number | void) => 42, 4) // ok
call((x: number | void, y: number | void) => 42, 4, 2) // ok


//// [callWithMissingVoid.js]
"use strict";
// From #4260
var X = /** @class */ (function () {
function X() {
}
X.prototype.f = function (t) {
return { a: t };
};
return X;
}());
x.f(); // no error because f expects void
xUnion.f(42); // no error because f accepts number
xUnion.f(); // no error because f accepts void
xAny.f(); // error, any still expects an argument
xUnknown.f(); // error, unknown still expects an argument
xNever.f(); // error, never still expects an argument
// Promise has previously been updated to work without arguments, but to show this fixes the issue too.
var MyPromise = /** @class */ (function () {
function MyPromise(executor) {
}
return MyPromise;
}());
new MyPromise(function (resolve) { return resolve(); }); // no error
new MyPromise(function (resolve) { return resolve(); }); // no error
new MyPromise(function (resolve) { return resolve(); }); // error, `any` arguments cannot be omitted
new MyPromise(function (resolve) { return resolve(); }); // error, `unknown` arguments cannot be omitted
new MyPromise(function (resolve) { return resolve(); }); // error, `never` arguments cannot be omitted
// Multiple parameters
function a(x, y, z) {
}
a(4, "hello"); // ok
a(4, "hello", void 0); // ok
a(4); // not ok
function b(x, y, z, what) {
}
b(4, "hello", void 0, 2); // ok
b(4, "hello"); // not ok
b(4, "hello", void 0); // not ok
b(4); // not ok
function c(x, y, z) {
}
c(3, void 0, void 0); // ok
c(3, void 0); // ok
c(3); // ok
c(); // ok
call(function (x, y) { return x + y; }); // error
call(function (x, y) { return x + y; }, 4, 2); // ok
call(function (x, y) { return x; }, 4, void 0); // ok
call(function (x, y) { return x; }, 4); // ok
call(function (x, y) { return 42; }); // ok
call(function (x, y) { return 42; }); // ok
call(function (x, y) { return 42; }, 4); // ok
call(function (x, y) { return 42; }, 4, 2); // ok
Loading