TypeScript: Type guard failed when using union typed variable for props

TypeScript Version: 4.0.2

I can’t open TypeScript Playground with ‘Nightly’ version so I created with the latest.

Search Terms: type guard failed, union props

Code

interface PluginHook {
    onStoreCreated?: (p1: number) => void;
    onModel?: (p2: string) => void;
}

interface MyPlugin extends PluginHook {
    config?: Record<string, unknown>;
}

type Method = keyof PluginHook;

function fn(p: NonNullable<PluginHook[Method]>) { }

const plugins: MyPlugin[] = [];

for (const plugin of plugins) {
    const method: Method = 'onStoreCreated';

    // worked
    if (plugin['onStoreCreated']) {
        fn(plugin['onStoreCreated'])
    }

    // failed
    // error: Argument of type '((p1: number) => void) | undefined' is not assignable to parameter of type '((p1: number) => void) | ((p2: string) => void)'.
    // Type 'undefined' is not assignable to type '((p1: number) => void) | ((p2: string) => void)'.(2345)
    if (plugin[method]) {
        fn(plugin[method])
    }
}

Expected behavior: Type guard checking successfully so plugin[method] won’t be undefined or null

Actual behavior: Type guard failed to check undefined for plugin[method].

Playground Link: Click here

Related Issues:

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 15 (5 by maintainers)

Most upvoted comments

@RyanCavanaugh In that case probably is a bug or design limitation (I’m not sure):

interface PluginHook {
    onStoreCreated?: (p1: string) => void;
    onModel?: (p2: string) => void;
}

declare const plugins: PluginHook[];

function func<Hook extends keyof PluginHook>(method: Hook, fn: (p: NonNullable<PluginHook[keyof PluginHook]>) => void): void {
    for (const plugin of plugins) {
        const hook = plugin[method];
        if (typeof hook !== "undefined") {
            fn(hook);
            // ^^^^
            /*
            Argument of type 'PluginHook[Hook]' is not assignable to parameter of type '((p1: string) => void) | ((p2: string) => void)'.
              Type '((p1: string) => void) | ((p2: string) => void) | undefined' is not assignable to type '((p1: string) => void) | ((p2: string) => void)'.
                Type 'undefined' is not assignable to type '((p1: string) => void) | ((p2: string) => void)'.
                  Type 'PluginHook[Hook]' is not assignable to type '(p2: string) => void'.
                    Type '((p1: string) => void) | ((p2: string) => void) | undefined' is not assignable to type '(p2: string) => void'.
                      Type 'undefined' is not assignable to type '(p2: string) => void'.
            */
        }
        if (hook) {
            fn(hook);
            // ^^^^
            /*
            Argument of type 'PluginHook[Hook]' is not assignable to parameter of type '((p1: string) => void) | ((p2: string) => void)'.
              Type 'PluginHook[Hook]' is not assignable to type '(p2: string) => void'.
            */
        }
        if (!!hook) {
            fn(hook);
            // ^^^^
            /*
            Argument of type 'PluginHook[Hook]' is not assignable to parameter of type '((p1: string) => void) | ((p2: string) => void)'.
              Type 'PluginHook[Hook]' is not assignable to type '(p2: string) => void'.
            */
        }
    }
}

Expected: It works

Actual: It say is not compatible. Changing the order of the if display the same errors in the exact order. The first error says that can be undefined just after checking for undefined.

Playground

Save the plugin[method] value into a const variable and use it:

    const v = plugin[method];
    if (v) {
        fn(v)
    }

Full code on playground.

Many thanks, mate

Save the plugin[method] value into a const variable and use it:

    const v = plugin[method];
    if (v) {
        fn(v)
    }

Full code on playground.