The first issue here is that the implementation signature of an overloaded function is allowed to be looser than any of the call signatures. And inside the implementation, the compiler only checks against the implementation signature. That means that inside your function, foo
and bar
are both independently of type boolean | undefined
and there is no way to recover the fact that anyone who calls the method will specify either both or neither.
TypeScript has recently added support for rest/spread tuples in function parameters, so you can rewrite your function signature like this:
declare function method(...args: [] | [boolean, boolean]);
method(); // okay
method(false); // errror
method(true, false); // okay
Now TypeScript knows that the args
to method()
are either the empty tuple or a pair of boolean
values. You can keep the overloads if you want, and just make the implementation signature narrower:
function method(): void;
function method(foo: boolean, bar: boolean): void;
function method(...args: [] | [boolean, boolean]) {
const foo = args[0];
const bar = args[1];
if (foo === true || foo === false) {
const result = bar; // oops, still boolean | undefined
}
}
Unfortunately the inference still doesn't work, and that's the second issue: TypeScript's control flow analysis is simply not as clever as we are. While we understand that the type of foo
is correlated with the type of bar
, the compiler does not. If narrows foo
but has forgotten that bar
has anything to do with foo
. One way to fix this is not to break apart foo
and bar
into separate types, but instead use property access type guards on the single args
variable. When args
gets narrowed from [] | [boolean, boolean]
to just [boolean, boolean]
, you can be sure that the second element is defined:
function method(): void;
function method(foo: boolean, bar: boolean): void;
function method(...args: [] | [boolean, boolean]) {
if ('0' in args) {
const result = args[1]; // boolean
}
}
This might all be too much code changing, and the IntelliSense isn't worth it to you. If so, and you are comfortable being smarter the compiler, you can just use a type assertion and move on with your day:
function method(): void;
function method(foo: boolean, bar: boolean): void;
function method(foo?: boolean, bar?: boolean) {
if (foo === true || foo === false) {
const result = bar as boolean; // I'm smarter than the compiler
}
}