Given this typesafe method accepting one argument, can you generalize it to N arguments while preserving type safety?

岁酱吖の 提交于 2020-01-24 20:48:44

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!