Search code examples
typescripttypescript-generics

Trying to optimize a code with conditional expression and can't make it work with [never]


I have the following code:

type Success<T> = { success: true, value: T }
type Failure<E> = { success: false, error: E }

type Result<T, E = never> = Success<T> | Failure<E>

function succeed<T>(value: T): Result<T, never> {
    return { success: true, value }
}
function fail<E>(error: E): Result<never, E> {
    return { success: false, error }
}

function test(value: string): Result<string, string> {
    if (value === undefined) {
        return fail("empty");
    } else {
        return succeed(value);
    }
}

I'm trying to optimize it so that when E is never, Result is considered as a Success result.

For this, I adapted the definition of Result with the following conditional expression:

type Result<T, E = never> = [E] extends [never]
  ? Success<T>
  : Success<T> | Failure<E>

Which raise a typescript error on the result of the fail method:

Type '{ success: false; error: E; }' is not assignable to type 'Result<never, E>'.

I am trying to correct this or find alternatives since a few days, but I don't understand why the conditional expression works for succeed (when E is never) and raise an error for the general case.


Solution

  • You encountered a case where TS doesn't support control flow for return conditional generic types. An easy solution would be use function overloads instead of a conditional type:

    Playground

    type Success<T> = { success: true, value: T }
    type Failure<E> = { success: false, error: E }
    
    function succeed<T>(value: T): Success<T> {
        return { success: true, value }
    }
    
    function fail<E>(error: E): Failure<E> {
        return { success: false, error }
    }
    
    type Simplify<T extends object> = {[K in keyof T]: T[K]} extends infer A ? A : never;
    
    function test<T extends undefined>(value?: T): Simplify<Failure<'empty'>>;
    function test<T extends string>(value: T): Simplify<Success<T>>;
    function test(value?: string) {
        if (value === undefined) {
            return fail("empty");
        } else {
            return succeed(value);
        }
    }
    
    const r = test(); // Failure
    const r2 = test(undefined); // Failure
    const r3 = test('data'); // Success