I'm trying to use typescript 2.8's new conditional types, (not yet released version 2.8.0-dev.20180307) and I don't know if this is a bug or just a misuse. My focus is on my declaration of MockedImplementation<F> which can be a full function matching F, the return type of F, and if the return type of F is a Promise, then it could also be what the promise resolves to - all to be wrapped accordingly by mockIt().
type MockedImplementation<F> =
F | // The function signature
((ReturnType<F extends (...args: any[]) => any ? F : any>) extends infer T
? T extends Promise<infer R>
? (T | R) // Or the promise or just the type that the promise resolves to
: T // or whatever type this is
: never);
interface Wrapped<T> {
result: T
}
function mockIt<F>(pretend : MockedImplementation<F>) : F {
throw new Error('Not Implemented'); // doesn't matter
}
interface SomeOperationA {
(parameters : { 'a': number[], 'b'?: string }) : Promise<string>;
}
mockIt<SomeOperationA>(() => Promise.resolve('hello')); // OK
mockIt<SomeOperationA>(Promise.resolve('hello')); // OK
mockIt<SomeOperationA>('hello'); // OK
mockIt<SomeOperationA>(42); // Type error.
mockIt of SomeOperationA works, perhaps directly because there are no function signature overrides. But mockIt of SomeOperationB fails:
interface SomeOperationB {
(parameters : { 'a': number[], 'b'?: string }) : Promise<string>;
(parameters : { 'a': number[], 'b'?: string }, rawResponse : true) : Promise<Wrapped<string>>;
(parameters : { 'a': number[], 'b'?: string }, rawResponse : false) : Promise<string>;
}
mockIt<SomeOperationB>(() => Promise.resolve('hello')); // ❌ Type 'string' is not assignable to type 'Wrapped<string>'.
mockIt<SomeOperationB>(Promise.resolve('hello')); // OK
mockIt<SomeOperationB>('hello'); // OK
mockIt<SomeOperationB>(42); // Type error.
It seems to be intersecting types instead of unioning them? But I'm sure it is more nuanced than that.
I saw a note somewhere about "considers the last overload because presumably it is the most generalized", but I don't think it is relevant here because it doesn't seem to behave as though it matters.
Edit
@jcalz is right, makes sense:
interface SomeOperationB {
(wrapped : true) : Promise<Wrapped<string>>;
(wrapped : false) : Promise<string>;
}
interface Wrapped<T> { result: T }
declare function acceptSomeOperationB(x: SomeOperationB): void;
acceptSomeOperationB(() => Promise.resolve('hello')); // ❌ Type 'string' is not assignable to type 'Wrapped<string>'.
acceptSomeOperationB(() => Promise.resolve({ result: 'hello' })); // ❌ Type '{ result: string; }' is not assignable to type 'string'.
A smaller reproduction of your problem with no conditional types:
It is clear that TypeScript does not consider the arrow function to be compatible with
SomeOperationBbecause it fails to meet one of the overloaded signatures. Indeed, if you passtrueas the second parameter to that function, it will not return aPromise<Wrapped<string>>, as required by the second signature forSomeOperationB.Once you decide how to resolve that, it should start working (or you can at least move on to issues with conditional types.)
Good luck.