Skip to content

Commit

Permalink
Merge pull request #27522 from jack-williams/trailing-void-args-are-o…
Browse files Browse the repository at this point in the history
…ptional

Fix #4260 : Allow trailing arguments that accept void to be omitted
  • Loading branch information
RyanCavanaugh authored Oct 19, 2018
2 parents 8779d4c + 8500f7c commit b64d08a
Show file tree
Hide file tree
Showing 6 changed files with 913 additions and 2 deletions.
16 changes: 14 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18816,6 +18816,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 @@ -18877,8 +18881,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

0 comments on commit b64d08a

Please sign in to comment.