Reputation: 6372
I thought it would be possible to use typescript overloaded function type signatures to express a situation where the 3rd parameter type changes when the 1st parameter type changes.
type ActionAsync<S> = {
(order: boolean, seek: S, limit: number): Promise<number>
(order: null, seekAfter: S, seekBefore: S): Promise<number>
};
async function foo() {
return 5
}
const x: ActionAsync<Date> = async (order: boolean|null, seekOrSeekAfter: Date, limitOrSeekBefore: number | Date) => {
if (order === null) {
return foo();
}
if (order === true) {
return limitOrSeekBefore + 43;
} else if (order === false) {
return foo();
}
}
You can see that limitOrSeekBefore
should be number
if the order
is boolean
, but should be S
or Date
when order
is null
.
However this doesn't typecheck. It only does if I change to limitOrSeekBefore: any
.
Upvotes: 0
Views: 56
Reputation: 328473
If by "it doesn't type check" you mean that the compiler doesn't understand that order === true
implies that limitOrSeekBefore
is a number
, then this is just a design limitation in TypeScript. If you assign an arrow function to a variable with multiple call signatures, the compiler does not perform control flow analysis for each call signature inside the implementation. It just does one pass based on the implementation signature, and since order
and limitOrSeekBefore
are uncorrelated union types there, the compiler cannot do much to verify anything for you.
The canonical issue for this is microsoft/TypeScript#38622, where the word-of-tech-lead is
The only way to detect that this is a valid assignment is to read the function body and understand that it behaves correctly in each overload case; it is far beyond TS's capabilities to do this.
Note that in your case the same problem happens with "normal" overloads:
async function y(order: boolean, seek: Date, limit: number): Promise<number>;
async function y(order: null, seekAfter: Date, seekBefore: Date): Promise<number>;
async function y(order: boolean | null, seekOrSeekAfter: Date, limitOrSeekBefore: number | Date) {
if (order === null) {
return foo();
}
if (order === true) {
return limitOrSeekBefore + 43; // error
} else {
return foo();
}
}
The compiler only does control flow analysis based on the implementation signature, not the call signatures. Normal overloads are a bit looser in that you can return a value that is only applicable to some of the call signatures, but since your return value is Promise<number>
in all cases, there's no difference in behavior here.
To make it work, I suggest that you use a type assertion to tell the compiler that you know that limitOrSeekBefore
is a number
and that it shouldn't worry:
const x: ActionAsync<Date> = async (order: boolean | null, seekOrSeekAfter: Date, limitOrSeekBefore: number | Date) => {
if (order === null) {
return foo();
}
if (order === true) {
return limitOrSeekBefore as number + 43;
} else {
return foo();
}
}
Upvotes: 1