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:
Duplicate of #10530 (and many many more). Type narrowing does not occur for indexed access forms e[k] where k is not a literal.
Thanks, closed this.
Duplicate of #10530 (and many many more). Type narrowing does not occur for indexed access forms
e[k]wherekis not a literal.
But do you know how to make it work if I coding this way?
Save the plugin[method] value into a const variable and use it:
const v = plugin[method];
if (v) {
fn(v)
}
Full code on 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
I have a more complicated scenario, @j-oliveras could you help me resolve this. Thanks in advance.
Reopened for help.
In that case I will use the a Definite Assignment Assertion:
if (hook) {
fn(hook!) // see the ! here
}
Playground
I don't know if exists a better solution/workaround.
PD: If you have question, better ask on Stackoverflow.
This doesn't appear to be describing a bug
This doesn't appear to be describing a bug
well, I will also ask on Stackoverflow, maybe it was a bug, see this please.
@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.
It was strange, I asked on StackOverflow and received a comment saying it works by using nullish coalescing operator like this: const hook = plugin[method] ?? undefined. But we don't know why. Full code on Playground
That was like non-null assertion operator ! or nullish coalescing operator ?? construct a NonNullable type, so it was compatible with the param which was NonNullable<T>. I will use !.
But I think the if block should construct one for us.
const someConstVariable: T = value
if (someConstVariable) {
const nonNullVariable = someConstVariable
}
The type of nonNullVariable should be NonNullable<T>.
This issue has been marked as 'Question' and has seen no recent activity. It has been automatically closed for house-keeping purposes. If you're still waiting on a response, questions are usually better suited to stackoverflow.