I've hit a problem with type-inference specifically when conditional-types are used within union types.
There may be a shorter way to demonstrate this issue, but I could not find one...
See the problem in action at this playground link.
Consider the following Result<T>
, a union-type used to indicate the success or failure of an operation (with an optionally attached value, of type T
). For the success-case, I have used the conditional type SuccessResult<T>
, which resolves to either OKResult
or ValueResult<T>
(depending on whether the result should also carry an attached value
):
type Result<T = undefined> = SuccessResult<T> | ErrorResult;
interface OKResult {
type: 'OK';
}
interface ValueResult<T> {
type: 'OK';
value: T;
}
interface ErrorResult {
type: 'Error';
error: any;
}
type SuccessResult<T = undefined> = T extends undefined ? OKResult : ValueResult<T>;
function isSuccess<T>(result: Result<T>): result is SuccessResult<T> {
return result.type === 'OK';
}
Let's use it with a simple union type:
type C1 = "A1" | "B1";
function makeC1(): C1 { return "A1" }
const c1: C1 = makeC1();
const c1Result: Result<C1> = { type: "OK", value: c1 }; // ALL IS GOOD
Now, instead of the simple union type C1
, which is just "A1" | "B1"
, let use a union type of complex values, C2
, in exactly the same way:
type A2 = {
type: 'A2';
}
type B2 = {
type: 'B2';
}
type C2 = A2 | B2;
function makeC2(): C2 { return { type: "A2" } }
const c2: C2 = makeC2();
const c2Result: Result<C2> = { type: "OK", value: c2 }; // OH DEAR!
This results in an error:
Type 'C2' is not assignable to type 'B2'.
Type 'A2' is not assignable to type 'B2'.
Types of property 'type' are incompatible.
Type '"A2"' is not assignable to type '"B2"'.
If I remove conditional typing from the equation and define my Result<T>
to use ValueResult<T>
instead of SuccessResult<T>
:
type Result<T = undefined> = ValueResult<T> | ErrorResult;
...everything works again, but I lose the ability to signal valueless success. This would be a sad fallback if I can't get the optional typing to work in this case.
Where did I go wrong? How can I use SuccessResult<T>
in the Result<T>
union, where T
itself is a complex union type?
OKResult
instead asValueResult<undefined>
, but hopefully this will be useful to someone else. – Rameriz