Search code examples
typescriptcovariance

Why does a type of T[keyof T] prevent covariance in an interface


I am trying to make a type covariant so that Type<Cat> can be treated as a Type<Animal> for example (like a list of Cats is a list of Animals). But for some reason, using the type T[keyof T] in a method in the Type interface seems to prevent that.

type Animal = {
    legs: boolean;
}

type Dog = Animal & {
    golden: boolean;
}

//This exhibits covariance
interface Comparer<T> {
    compare(a: T, b: T): number;
}

//this is valid
declare let animalComparer: CovariantComparer<Animal>;
declare let dogComparer: CovariantComparer<Dog>;

animalComparer = dogComparer;
dogComparer = animalComparer;  

//This does not
interface NonCovariantComparer<T> {
    compare(a: T, b: T[keyof T]): number;
}

declare let animalComparer2: NonCovariantComparer<Animal>;
declare let dogComparer2: NonCovariantComparer<Dog>;

animalComparer2 = dogComparer2; //typeerror
dogComparer2 = animalComparer2; //typeerror

Is there a way to make that work? Or if not, why is it impossible and is there a good pattern to get around this issue? Thanks!

Note: I'm working off of the assumption that making the object a method will make it bivariant

Typescript playground example


Solution

  • Adding a generic type U extends T on the compare() method of the interface seems to resolve the errors:

    type Animal = {
        legs: boolean;
    }
    
    type Dog = Animal & {
        golden: boolean;
    }
    
    interface CovariantComparer<T> {
        compare<U extends T>(a: T, b: U[keyof T]): number;
    }
    
    declare let animalComparer: CovariantComparer<Animal>;
    declare let dogComparer: CovariantComparer<Dog>;
    
    animalComparer = dogComparer;
    dogComparer = animalComparer;
    

    TypeScript Playground Link