问题
Background:
This is a follow up to two previous questions: 1, 2
Interface Extendable is generic on type T and has method extend that accepts an argument of type U and returns an argument of type V:
interface Extendable<T> {
extend<U, V>(x: U): V
}
Method extend has the following constraints:
- U must be assignable to T or T must be assignable to U.
- If U is assignable to T then V has type T. Otherwise V has type U.
Interface Extendable is updated below to reflect these constraints:
interface Extendable<T> {
extend<U extends (T extends U ? unknown : T)>(x: U): U extends T ? T : U
}
Interface Extendable is tested as follows:
interface A { a: string }
interface B extends A { b: string }
interface C extends B { c: string }
interface D extends A { d: string }
interface E { e: string }
declare let a: A, b: B, c: C, d: D, e: E
declare let t: Extendable<B>
t.extend(a) // return type A
t.extend(b) // return type B
t.extend(c) // return type B
t.extend(d) // error - D is not assignable to B
t.extend(e) // error - E is not assignable to B
Now it turns out, I'm building a typed SQL Query builder and queries are extendable. The following is an inverted query type hierarchy:
+Root
+---Filter
| +---Select
| +---Update
| \---Delete
\---Insert
Inheritance among query types is implicit through duck-typing, not explicit extension.
Root is assignable to Filter and Insert. Filter is assignable to Select, Update, and Delete.
You start with query type Root. If you construct a WHERE clause, the new query type becomes Filter. If you then construct a DELETE clause, the new query type becomes Delete.
The interfaces are below.
interface Root extends Extendable<Root> {
where(): Filter
select(): Select
insert(): Insert
update(): Update
delete(): Delete
}
interface Filter extends Extendable<Filter> {
where(): Filter
select(): Select
update(): Update
delete(): Delete
}
interface Select extends Extendable<Select> {
where(): Select
select(): Select
}
interface Update extends Extendable<Update> {
where(): Update
update(): Update
}
interface Delete extends Extendable<Delete> {
where(): Delete
delete(): Delete
}
interface Insert extends Extendable<Insert> {
insert(): Insert
}
The Query types are tested below:
declare let root: Root
declare let filter: Filter
declare let select: Select
declare let insert: Insert
filter.extend(root) // return type Filter
filter.extend(filter) // return type Filter
filter.extend(select) // return type Select
filter.extend(insert) // error - Insert is not assignable to Filter
The original constraints on the Extendable interface guarantee these two properties:
- A query may only be extended with a type that its own type, an ancestor type, or a descendent type
- When a query is extended, the return type is the argument type if the argument type is a descendent of the base type. Otherwise the return type is the base type.
Question:
I would like to add an additional type safe overload of extend that accepts multiple query arguments, e.g. extend(...args)
It would make the following two lines equivalent:
a.extend(b).extend(c).extend(d)
a.extend(b, c, d)
I don't know how to write the overload in a type safe way. Is it possible?
If it is not possible, is there a way to write an overload for two arguments? For three arguments? Can you generalize the procedure to N arguments?
来源:https://stackoverflow.com/questions/53016538/given-this-typesafe-method-accepting-one-argument-can-you-generalize-it-to-n-ar