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?
Playground link
type Result<T = undefined> = SuccessResult<T> | ErrorResult;
needs to be
type Result<T = undefined> = SuccessResult<T> | ErrorResult | ValueResult<T>;
then it compiles.
Cheers, Mike
Unfortunately specifying the return value of a function is not enough. You need to explicitly return the type. Then it compiles.
This doesn't work
function makeC2(): C2 {
return {
type: "A2"
};
};
This works
function makeC2() {
const x: C2 = {
type: "A2"
};
return x;
};
Playground link
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With