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 |
I can make it so that Query
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 {
extend(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;
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 = (U extends any ? [T] extends [U] ? never : unknown : never)
type AbsorbUnion = [T] extends [infer U] ? U extends any ?
NotExtendsAll extends never ? U : never : never : never
type Absorb = AbsorbUnion<{ [K in keyof T]: [T[K]] }[number]>[0];
type AsArray = [T] extends [any[]] ? T : never;
interface Query {
extend>(
...x: U
): Absorb extends never ? T : Absorb;
}
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!