Is it possible to declare a function that accepts superclasses of a given type?

后端 未结 1 383
时光取名叫无心
时光取名叫无心 2021-01-15 21:31

I\'m writing type declarations for a Javascript library.

I have an interface that is generic on type T. It has a function with an argument that should have type (T |

相关标签:
1条回答
  • 2021-01-15 22:02

    I can make it so that Query<T>.extend() only takes parameters that are either assignable to T or those that T is assignable to. That is, either a supertype or a subtype. It uses (as these things often do) conditional types:

    interface Query<T> {
      extend<U extends (T extends U ? unknown : T)>(x: U): U;
    }
    

    Let's try it:

    class A { a: string = "a" }
    class B extends A { b: string = "b" }
    class C extends B { c: string = "c" }
    
    class D extends A { d: string = "d" }
    class E { e: string = "e" }
    
    declare let a: A;
    declare let b: B;
    declare let c: C;
    declare let d: D;
    declare let e: E;
    
    declare let qb: Query<B>;
    qb.extend(a); // okay, B extends A
    qb.extend(b); // okay, B extends B
    qb.extend(c); // okay, C extends B
    qb.extend(d); // error, D not assignable to B
    qb.extend(e); // error, E not assignable to B
    qb.extend(Math.random() < 0.5 ? a : e); // okay, B extends A | E
    

    Looks reasonable to me. Hope that's useful to you. Good luck!


    UPDATE: combining this with my answer to the other question about accepting a number of arguments and returning "the most specific one", with all the caveats and craziness from that question, gives you:

    type NotExtendsAll<T, U> = (U extends any ? [T] extends [U] ? never : unknown : never)
    type AbsorbUnion<T> = [T] extends [infer U] ? U extends any ?
      NotExtendsAll<U, T> extends never ? U : never : never : never
    type Absorb<T extends any[]> = AbsorbUnion<{ [K in keyof T]: [T[K]] }[number]>[0];
    
    type AsArray<T> = [T] extends [any[]] ? T : never;    
    interface Query<T> {
      extend<U extends AsArray<{ [K in keyof U]: T extends U[K] ? unknown : T }>>(
      ...x: U
      ): Absorb<U> extends never ? T : Absorb<U>;
    }
    

    Basically what I'm doing there is requiring U to be an array or tuple of types where each type is either a subtype or supertype of T, and it returns the type of the narrowest argument, or just T if there is no narrowest argument (zero arguments, or multiple arguments in a forked class hierarchy).

    All the above tests give the same results, and now you can also do this:

    qb.extend(a, a, a); // okay, returns type A
    qb.extend(b, a, b, b, a); // okay, returns type B 
    qb.extend(a, b, c, b, a); // okay, returns type C
    qb.extend(); // okay, returns type B
    

    Not sure about the implementation of Query; hopefully you can handle that.

    Good luck again!

    0 讨论(0)
提交回复
热议问题