Skip to content

null/undefined guards sometimes adds & ({} | undefined/& ({} | null (out of nowhere) to the tested variable. #63385

@denis-migdal

Description

@denis-migdal

🔎 Search Terms

guards
null undefined
type narrowing
generic function
type inside generic function

Searched for related issues, didn't found any.
Maybe this issue is somehow related to the type narrowing system ?

🕗 Version & Regression Information

  • This is the behavior in every version I tried.

⏯ Playground Link

Playground Link

💻 Code

See playground for full example.

type Hook  = (...args: any[]) => any;
export type Hooks = Record<string, Hook>;

function foo1<T extends WithHooks<any>>() {

    type Hooks = GetHooks<T>; // but works if in the generic template.
    const hook = {} as Hooks[keyof Hooks]; // type: GetHooks<T>[keyof GetHooks<T>]
    if( hook === undefined)
        return;

    hook(...null as any); // nok: adds '& ({} | null)' to the type.
}

🙁 Actual behavior

Somehow TS adds & | ({}|undefined) or & | ({}|null) to my type as if it was narrowing from & ({} | undefined | null)... but where does this type would come from ???

If I test with hook === false and ! hook, the type isn't modified (cf playground). Also tested with typeof === "object", === 2, === "2", and with a personalized type guard is {}. Seems to only occurs when testing with undefined and null.

I understand that conditions sometimes breaks type narrowing, but I shouldn't have type narrowing here, right ?

🙂 Expected behavior

The type shouldn't change.

Additional information about the issue

Found this issue by accident.
Declaring a type inside a generic function isn't recommended, so this issue shouldn't have a big impact.

Still, this behavior quite surprises me, and might cause issues in other contexts (or be the cause of other strange behaviors).

Note: an easy workaround is to put the type definition inside the function generic types:

function foo5<T extends WithHooks<any>, MyHooks extends Hooks = GetHooks<T>>() {

    const hook = {} as MyHooks[keyof MyHooks];
    if( hook === undefined)
        return;

    hook(...null as any); // ok
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions