Skip to content

Type narrowing does not properly narrow type of generic class instanceΒ #47502

@caffeinewriter

Description

@caffeinewriter

Bug Report

πŸ”Ž Search Terms

code path, type narrowing

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about type narrowing

⏯ Playground Link

TypeScript Nightly
TypeScript v4.5.4
TypeScript v3.3.3 (no private fields)

πŸ’» Code

type GenericClass = new (...args: any) => any;

export default class SingletonFactory<T extends GenericClass> {
    readonly #instanceClass: T;
    #instance?: InstanceType<T>;
    constructor(instanceClass: T) {
        this.#instanceClass = instanceClass;
    }
    getInstance(options?: ConstructorParameters<T>): InstanceType<T> {
        if (this.#instance === undefined) {
            if (!options) {
                // Error if both instance and options are undefined
                throw new Error('No instance found, and no options provided');
            }
            // Create new instance if not defined, and options are provided
            this.#instance = new this.#instanceClass(options);
            return this.#instance; // Error: Type 'InstanceType<T> | undefined' is not assignable to type 'InstanceType<T>'.
        }
        return this.#instance;
    }
}

πŸ™ Actual behavior

After creating a new instance of this.#instanceClass, TypeScript still reports the type as possibly undefined.

πŸ™‚ Expected behavior

After creating a new instance and assigning it to this.#instance, the type of this.#instance should narrow to only InstanceType<T>. A workaround can be achieved by assigning the new instance to an intermediary variable, and then returning the intermediary variable instead.

Example playground of workaround

Additionally, even though the type of the property is InstanceType<T> | undefined, other similar situations properly narrow the code path. I've created an example using string | undefined here for comparison.

Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    Not a DefectThis behavior is one of several equally-correct options

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions