Skip to content

Commit b64d08a

Browse files
Merge pull request #27522 from jack-williams/trailing-void-args-are-optional
Fix #4260 : Allow trailing arguments that accept void to be omitted
2 parents 8779d4c + 8500f7c commit b64d08a

File tree

6 files changed

+913
-2
lines changed

6 files changed

+913
-2
lines changed

Diff for: src/compiler/checker.ts

+14-2
Original file line numberDiff line numberDiff line change
@@ -18816,6 +18816,10 @@ namespace ts {
1881618816
return findIndex(args, isSpreadArgument);
1881718817
}
1881818818

18819+
function acceptsVoid(t: Type): boolean {
18820+
return !!(t.flags & TypeFlags.Void);
18821+
}
18822+
1881918823
function hasCorrectArity(node: CallLikeExpression, args: ReadonlyArray<Expression>, signature: Signature, signatureHelpTrailingComma = false) {
1882018824
let argCount: number;
1882118825
let callIsIncomplete = false; // In incomplete call we want to be lenient when we have too few arguments
@@ -18877,8 +18881,16 @@ namespace ts {
1887718881

1887818882
// If the call is incomplete, we should skip the lower bound check.
1887918883
// JSX signatures can have extra parameters provided by the library which we don't check
18880-
const hasEnoughArguments = argCount >= effectiveMinimumArguments;
18881-
return callIsIncomplete || hasEnoughArguments;
18884+
if (callIsIncomplete || argCount >= effectiveMinimumArguments) {
18885+
return true;
18886+
}
18887+
for (let i = argCount; i < effectiveMinimumArguments; i++) {
18888+
const type = getTypeAtPosition(signature, i);
18889+
if (filterType(type, acceptsVoid).flags & TypeFlags.Never) {
18890+
return false;
18891+
}
18892+
}
18893+
return true;
1888218894
}
1888318895

1888418896
function hasCorrectTypeArgumentArity(signature: Signature, typeArguments: NodeArray<TypeNode> | undefined) {
+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts(16,1): error TS2554: Expected 1 arguments, but got 0.
2+
tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts(19,1): error TS2554: Expected 1 arguments, but got 0.
3+
tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts(22,1): error TS2554: Expected 1 arguments, but got 0.
4+
tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts(35,31): error TS2554: Expected 1 arguments, but got 0.
5+
tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts(36,35): error TS2554: Expected 1 arguments, but got 0.
6+
tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts(37,33): error TS2554: Expected 1 arguments, but got 0.
7+
tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts(48,1): error TS2554: Expected 3 arguments, but got 1.
8+
tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts(55,1): error TS2554: Expected 4 arguments, but got 2.
9+
tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts(56,1): error TS2554: Expected 4 arguments, but got 3.
10+
tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts(57,1): error TS2554: Expected 4 arguments, but got 1.
11+
tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts(75,1): error TS2554: Expected 3 arguments, but got 1.
12+
13+
14+
==== tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts (11 errors) ====
15+
// From #4260
16+
class X<T> {
17+
f(t: T) {
18+
return { a: t };
19+
}
20+
}
21+
22+
declare const x: X<void>;
23+
x.f() // no error because f expects void
24+
25+
declare const xUnion: X<void | number>;
26+
xUnion.f(42) // no error because f accepts number
27+
xUnion.f() // no error because f accepts void
28+
29+
declare const xAny: X<any>;
30+
xAny.f() // error, any still expects an argument
31+
~~~~~~~~
32+
!!! error TS2554: Expected 1 arguments, but got 0.
33+
!!! related TS6210 tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts:3:7: An argument for 't' was not provided.
34+
35+
declare const xUnknown: X<unknown>;
36+
xUnknown.f() // error, unknown still expects an argument
37+
~~~~~~~~~~~~
38+
!!! error TS2554: Expected 1 arguments, but got 0.
39+
!!! related TS6210 tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts:3:7: An argument for 't' was not provided.
40+
41+
declare const xNever: X<never>;
42+
xNever.f() // error, never still expects an argument
43+
~~~~~~~~~~
44+
!!! error TS2554: Expected 1 arguments, but got 0.
45+
!!! related TS6210 tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts:3:7: An argument for 't' was not provided.
46+
47+
48+
// Promise has previously been updated to work without arguments, but to show this fixes the issue too.
49+
50+
class MyPromise<X> {
51+
constructor(executor: (resolve: (value: X) => void) => void) {
52+
53+
}
54+
}
55+
56+
new MyPromise<void>(resolve => resolve()); // no error
57+
new MyPromise<void | number>(resolve => resolve()); // no error
58+
new MyPromise<any>(resolve => resolve()); // error, `any` arguments cannot be omitted
59+
~~~~~~~~~
60+
!!! error TS2554: Expected 1 arguments, but got 0.
61+
!!! related TS6210 tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts:28:38: An argument for 'value' was not provided.
62+
new MyPromise<unknown>(resolve => resolve()); // error, `unknown` arguments cannot be omitted
63+
~~~~~~~~~
64+
!!! error TS2554: Expected 1 arguments, but got 0.
65+
!!! related TS6210 tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts:28:38: An argument for 'value' was not provided.
66+
new MyPromise<never>(resolve => resolve()); // error, `never` arguments cannot be omitted
67+
~~~~~~~~~
68+
!!! error TS2554: Expected 1 arguments, but got 0.
69+
!!! related TS6210 tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts:28:38: An argument for 'value' was not provided.
70+
71+
72+
// Multiple parameters
73+
74+
function a(x: number, y: string, z: void): void {
75+
76+
}
77+
78+
a(4, "hello"); // ok
79+
a(4, "hello", void 0); // ok
80+
a(4); // not ok
81+
~~~~
82+
!!! error TS2554: Expected 3 arguments, but got 1.
83+
!!! related TS6210 tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts:42:23: An argument for 'y' was not provided.
84+
85+
function b(x: number, y: string, z: void, what: number): void {
86+
87+
}
88+
89+
b(4, "hello", void 0, 2); // ok
90+
b(4, "hello"); // not ok
91+
~~~~~~~~~~~~~
92+
!!! error TS2554: Expected 4 arguments, but got 2.
93+
!!! related TS6210 tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts:50:34: An argument for 'z' was not provided.
94+
b(4, "hello", void 0); // not ok
95+
~~~~~~~~~~~~~~~~~~~~~
96+
!!! error TS2554: Expected 4 arguments, but got 3.
97+
!!! related TS6210 tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts:50:43: An argument for 'what' was not provided.
98+
b(4); // not ok
99+
~~~~
100+
!!! error TS2554: Expected 4 arguments, but got 1.
101+
!!! related TS6210 tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts:50:23: An argument for 'y' was not provided.
102+
103+
function c(x: number | void, y: void, z: void | string | number): void {
104+
105+
}
106+
107+
c(3, void 0, void 0); // ok
108+
c(3, void 0); // ok
109+
c(3); // ok
110+
c(); // ok
111+
112+
113+
// Spread Parameters
114+
115+
declare function call<TS extends unknown[]>(
116+
handler: (...args: TS) => unknown,
117+
...args: TS): void;
118+
119+
call((x: number, y: number) => x + y) // error
120+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
121+
!!! error TS2554: Expected 3 arguments, but got 1.
122+
!!! related TS6210 tests/cases/conformance/expressions/functionCalls/callWithMissingVoid.ts:73:5: An argument for 'args' was not provided.
123+
call((x: number, y: number) => x + y, 4, 2) // ok
124+
125+
call((x: number, y: void) => x, 4, void 0) // ok
126+
call((x: number, y: void) => x, 4) // ok
127+
call((x: void, y: void) => 42) // ok
128+
call((x: number | void, y: number | void) => 42) // ok
129+
call((x: number | void, y: number | void) => 42, 4) // ok
130+
call((x: number | void, y: number | void) => 42, 4, 2) // ok
131+

Diff for: tests/baselines/reference/callWithMissingVoid.js

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
//// [callWithMissingVoid.ts]
2+
// From #4260
3+
class X<T> {
4+
f(t: T) {
5+
return { a: t };
6+
}
7+
}
8+
9+
declare const x: X<void>;
10+
x.f() // no error because f expects void
11+
12+
declare const xUnion: X<void | number>;
13+
xUnion.f(42) // no error because f accepts number
14+
xUnion.f() // no error because f accepts void
15+
16+
declare const xAny: X<any>;
17+
xAny.f() // error, any still expects an argument
18+
19+
declare const xUnknown: X<unknown>;
20+
xUnknown.f() // error, unknown still expects an argument
21+
22+
declare const xNever: X<never>;
23+
xNever.f() // error, never still expects an argument
24+
25+
26+
// Promise has previously been updated to work without arguments, but to show this fixes the issue too.
27+
28+
class MyPromise<X> {
29+
constructor(executor: (resolve: (value: X) => void) => void) {
30+
31+
}
32+
}
33+
34+
new MyPromise<void>(resolve => resolve()); // no error
35+
new MyPromise<void | number>(resolve => resolve()); // no error
36+
new MyPromise<any>(resolve => resolve()); // error, `any` arguments cannot be omitted
37+
new MyPromise<unknown>(resolve => resolve()); // error, `unknown` arguments cannot be omitted
38+
new MyPromise<never>(resolve => resolve()); // error, `never` arguments cannot be omitted
39+
40+
41+
// Multiple parameters
42+
43+
function a(x: number, y: string, z: void): void {
44+
45+
}
46+
47+
a(4, "hello"); // ok
48+
a(4, "hello", void 0); // ok
49+
a(4); // not ok
50+
51+
function b(x: number, y: string, z: void, what: number): void {
52+
53+
}
54+
55+
b(4, "hello", void 0, 2); // ok
56+
b(4, "hello"); // not ok
57+
b(4, "hello", void 0); // not ok
58+
b(4); // not ok
59+
60+
function c(x: number | void, y: void, z: void | string | number): void {
61+
62+
}
63+
64+
c(3, void 0, void 0); // ok
65+
c(3, void 0); // ok
66+
c(3); // ok
67+
c(); // ok
68+
69+
70+
// Spread Parameters
71+
72+
declare function call<TS extends unknown[]>(
73+
handler: (...args: TS) => unknown,
74+
...args: TS): void;
75+
76+
call((x: number, y: number) => x + y) // error
77+
call((x: number, y: number) => x + y, 4, 2) // ok
78+
79+
call((x: number, y: void) => x, 4, void 0) // ok
80+
call((x: number, y: void) => x, 4) // ok
81+
call((x: void, y: void) => 42) // ok
82+
call((x: number | void, y: number | void) => 42) // ok
83+
call((x: number | void, y: number | void) => 42, 4) // ok
84+
call((x: number | void, y: number | void) => 42, 4, 2) // ok
85+
86+
87+
//// [callWithMissingVoid.js]
88+
"use strict";
89+
// From #4260
90+
var X = /** @class */ (function () {
91+
function X() {
92+
}
93+
X.prototype.f = function (t) {
94+
return { a: t };
95+
};
96+
return X;
97+
}());
98+
x.f(); // no error because f expects void
99+
xUnion.f(42); // no error because f accepts number
100+
xUnion.f(); // no error because f accepts void
101+
xAny.f(); // error, any still expects an argument
102+
xUnknown.f(); // error, unknown still expects an argument
103+
xNever.f(); // error, never still expects an argument
104+
// Promise has previously been updated to work without arguments, but to show this fixes the issue too.
105+
var MyPromise = /** @class */ (function () {
106+
function MyPromise(executor) {
107+
}
108+
return MyPromise;
109+
}());
110+
new MyPromise(function (resolve) { return resolve(); }); // no error
111+
new MyPromise(function (resolve) { return resolve(); }); // no error
112+
new MyPromise(function (resolve) { return resolve(); }); // error, `any` arguments cannot be omitted
113+
new MyPromise(function (resolve) { return resolve(); }); // error, `unknown` arguments cannot be omitted
114+
new MyPromise(function (resolve) { return resolve(); }); // error, `never` arguments cannot be omitted
115+
// Multiple parameters
116+
function a(x, y, z) {
117+
}
118+
a(4, "hello"); // ok
119+
a(4, "hello", void 0); // ok
120+
a(4); // not ok
121+
function b(x, y, z, what) {
122+
}
123+
b(4, "hello", void 0, 2); // ok
124+
b(4, "hello"); // not ok
125+
b(4, "hello", void 0); // not ok
126+
b(4); // not ok
127+
function c(x, y, z) {
128+
}
129+
c(3, void 0, void 0); // ok
130+
c(3, void 0); // ok
131+
c(3); // ok
132+
c(); // ok
133+
call(function (x, y) { return x + y; }); // error
134+
call(function (x, y) { return x + y; }, 4, 2); // ok
135+
call(function (x, y) { return x; }, 4, void 0); // ok
136+
call(function (x, y) { return x; }, 4); // ok
137+
call(function (x, y) { return 42; }); // ok
138+
call(function (x, y) { return 42; }); // ok
139+
call(function (x, y) { return 42; }, 4); // ok
140+
call(function (x, y) { return 42; }, 4, 2); // ok

0 commit comments

Comments
 (0)