Search code examples
typescriptundefined

How to distinguish `(() => any) | undefined` from just `() => any` in TypeScript?


I have two types: (() => any) | undefined and () => any. I would like extract the return type of the function but only if the value is definitely not undefined.

I have tried the following:

type IsUndefined<T> = T extends (() => any) ? "no" : "yes";

But this resolves to "yes" | "no" when the type is not undefined. I want to detect the difference between these types without creating a union.

Please see this playground link for an example.

That is the short story, the long story is that I have a struct like the following:

type MyStruct = {
    optionalHook?: ((...args: any) => any) | undefined,
    requiredHook: (...args: any) => any,
}

I would like to extract the return type of the optional hook, but only when it exists. I would like to extract the return type of the required hook otherwise.

See this playground link for a more comprehensive example.


Solution

  • Not sure why the absence of optionalHook doesn't infer T to undefined in createStruct but I've managed to get the desired result by adding default generic parameters:

    // Create the generic struct
    type MyStruct<T extends ((...args: any) => any) | undefined, U extends (...args: any) => any> = {
        optionalHook?: T,
        requiredHook: U,
    }
    
    // Utility function for easily creating stucts with the correct type.
    // Without this I'd have to type the generic arguments manually for every struct instance.
    function createStruct<T extends ((...args: any) => any) | undefined = undefined, U extends (...args: any) => any = (...args: any) => any>(struct: MyStruct<T, U>) {
        return struct;
    }
    
    const withOptional = createStruct({
        optionalHook: () => 5,
        requiredHook: () => "hello",
    });
    
    const withoutOptional = createStruct({
        requiredHook: () => true,
    });
    
    
    // The function for extracting the return type
    type getReturnType<T> =
        T extends MyStruct<infer O, infer R> ?
            O extends (...args: any) => infer OR ?
                OR :
                R extends (...args: any) => infer RR ? RR : never :
            never;
    
    
    type ResultWithOptional = getReturnType<typeof withOptional>;
    type ResultWithoutOptional = getReturnType<typeof withoutOptional>;
    

    TypeScript playground

    Explicitly set optionalHook to undefined also works:

    TypeScript playground