Skip to content

Custom type guard / generic predicate leaks outside its scope in certain settingsΒ #43719

@mckravchyk

Description

@mckravchyk

Bug Report

πŸ”Ž Search Terms

generic predicate custom type guard

πŸ•— Version & Regression Information

Occurs in versions 4.2, 4.3 and nightly

In version 4.1 the custom type guard isNumClass didn't work at all inside ComplexStore.get

⏯ Playground Link (full example)

Playground link with full code

πŸ’» Code (partial)

Small code sample (please see the playground link for full example).

// This is only a chunk of the problem case, please see the playground link for full code

const isNumClass = <Item extends NumClass<number> | StrClass<string>> (
        item: Item
    ): item is Extract<Item, NumClass<any>> => {
        return (item instanceof NumClass);
    }

/**
 * A an example with 2-dimensional dictionary.
 * 
 * In v4.1 the `isNumClass` type guard doesn't work at all.
 * In v4.2 or later, `isNumClass` type guard leaks outside its
 * scope.
 */
class ComplexStore<Slices extends { [index: string]: Slice }> {
    private slices = { } as Slices;

    public get<SliceId extends keyof Slices, SliceKey extends keyof Slices[SliceId]>(
        sliceId: SliceId, sliceKey: SliceKey
    ): Slices[SliceId][SliceKey] {
        let item = this.slices[sliceId][sliceKey];

        if (isNumClass(item)) {
            item.numExclusive(); // works only since version 4.2
        }

        // unfortunately, doesn't work completely.
        // it seems like item's predicated type leaks outside the bracket...
        
        return item; // type is Extract ...
    }

    public get2<SliceId extends keyof Slices, SliceKey extends keyof Slices[SliceId]>(
        sliceId: SliceId, sliceKey: SliceKey
    ): Slices[SliceId][SliceKey] {
        let item = this.slices[sliceId][sliceKey];

        if (isNumClass(item)) {
            return item;
        }
        // it seems like the compiler asumes the above condition is always
        // truthy

        return item; // type is never
    }
}

πŸ™ Actual behavior

  • The type predicate modifies the type outside its scope. This happens only in a more complex scenario when using generics in a class with a 2-dimensional dictionary. The problem does not occur in the same class with a one-dimensional dictionary.

πŸ™‚ Expected behavior

  • The narrowed type should not leak outside the scope. It should remain unchanged.

I'm sorry if it's not a bug, but I think there are high chances it is. The one-dimensional example works ok now, and worked on in the previous version. The 2-dimensional example didn't work at all prior 4.2 and now works, but there are issues with it

By the way, I'm aware I could use bare instance of, it appeared to work in this simplified example, but it didn't work in my real case (although I will continue to experiment with it).

Metadata

Metadata

Assignees

Labels

BugA bug in TypeScriptDomain: Indexed Access TypesThe issue relates to accessing subtypes via index accessDomain: check: Control FlowThe issue relates to control flow analysisFix AvailableA PR has been opened for this issue

Type

No type
No fields configured for issues without a type.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions