Skip to content

Generics: Cannot limit template param to specific types #20235

Closed

Description

Generics currently allow you to restrict the type of a template param to any type that extends some type, but as far as I know, there's no way to restrict the type of the template param to specific types.

For example, I'm trying to create a generic type where the template param is one of any specific type in a discriminated union, but the compiler doesn't treat the type as a discriminated union within generic code. Here's is a super simplified example:

interface Cat {
    type: "cat"
    meow(): void;
}

interface Fish {
    type: "fish"
    swim(): void;
}

// discriminated union
type Animal = Cat | Fish;

// template param should really be limited to exactly Animal (Cat or Fish)
interface AnimalWrapper<T extends Animal> {
    animal: T;
}

function foo<T extends Animal>(animalWrapper: AnimalWrapper<T>) {
    if (animalWrapper.animal.type === "cat") {
        // ERROR: Property 'meow' does not exist on type 'T'  
        animalWrapper.animal.meow()
    } else {
        // ERROR: Property 'swim' does not exist on type 'T'  
        animalWrapper.animal.swim()
    }
}

Is it reasonable to expect that my code example should work, and that this is a bug? Or is there some technicality about T extending Animal rather than being Animal that prevents the compiler from making the typical assumptions that would allow my code example to work?

On the surface, it's hard for me to imagine how anything could possibly extend Animal, and have any value for type other than "cat" or "fish". Shouldn't the compiler be able to determine in my "if cat" statement that animalWrapper.animal is indeed something that extends Cat, and therefore must have the "meow" method?

Would it be reasonable to expand the syntax of Generics to be able to limit the template param to specific types? Here's what I imagine:

// "is" would indicate that "T" must be exactly one of the types in the "Animal" union.
// Or maybe it would make more sense to use "in" instead of "is"?
interface AnimalWrapper<T is Animal> {
    animal: T;
}

function foo<T is Animal>(animalWrapper: AnimalWrapper<T>) {
    // Now that "T" is limited specifically to Cat or Fish, the following code should be valid
    if (animalWrapper.animal.type === "cat") {
        // Compiler should now know BOTH:
        // 1) animalWrapper.animal is of type 'Cat'
        // 2) animalWrapper is of type AnimalWrapper<Cat>
        animalWrapper.animal.meow()
    } else {
        animalWrapper.animal.swim()
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    QuestionAn issue which isn't directly actionable in code

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions