如何键入递归可变参数元组

How to Type a Recursive Variadic Tuple

我有一个查询元素数组,其中每个元素可以是一个术语或包含以“AND”或“OR”开头的子查询,然后是合法查询元素、术语或嵌套子查询的数组,等等。

比如这些应该都是合法输入:

const query1 = "Hello"
const query2 = ["AND", "Hello", "World"]
const query3 = ["OR", ["AND", "Hello", "World"], ["AND", "Hola", "Mundo"]]

Variadric Tuples in TS 4.0 应该允许你键入数组的第一个元素,其余的类型为另一种类型:

type IQuery = ["AND" | "OR", ...Array<string>]
const query = ["AND", "Hello", "World"]  // valid

Recursive Types in TS 3.7 应该允许您定义一个使用自身的类型:

type IQueryOps = string | Array<IQueryOps>
const query: IQueryOps = ["Hello", "World", ["Hola", "Mundo"]]  // valid

但是当循环类型被传播时,我似乎无法将两者结合起来。在这种情况下,每个查询都以一个运算符开头,然后是一个字符串或另一个有效的查询,如下所示:

type IOperator = "AND" | "OR"
type IQuery = [IOperator, ...Array<string | IQuery>]

在这种情况下,我得到错误:

Type alias 'IQuery' circularly references itself.(2456)

有没有办法输入这个,即使有变通办法,或者我是否必须将它展开到我希望从类型角度支持的所需深度级别?

Demo in TS Playground

进一步阅读

我认为这可能是 microsoft/TypeScript#41164. As mentioned there

中报告的 TypeScript 设计限制的一个实例

Certain circularities are allowed [...] but other circularities aren't, e.g.

type Identity<T> = T;
type T3 = Identity<T3>;

Generic instantiation is deferred, so at the point TS analyzes [a] declaration, it can't tell if it's in the Record case (would be allowed) or the Identity case (would not be allowed). It's only later in the process that we could tell that this is actually OK, but if it wasn't OK, it's "too late" to go back and start complaining about it.

如果这是问题所在,那么虽然以下内容应该“没问题”,但似乎编译器无法及早告诉它以允许它:

// type IQuery = string | ["AND" | "OR", ...Array<IQuery>] error

(我稍微更改了您的定义以允许 const query1 = "Hello" 行)。


幸运的是,Array<T> 有另一种内置语法 T[],它似乎不会以这种方式延迟:

type IQuery = string | ["AND" | "OR", ...IQuery[]] // okay

这有效:

const query1: IQuery = "Hello"
const query2: IQuery = ["AND", "Hello", "World"]
const query3: IQuery = ["OR", ["AND", "Hello", "World"], ["AND", "Hola", "Mundo"]]
const query4: IQuery = ["AND", ["OR", ["AND"]]]; 

const badQuery1: IQuery = 3; // error
// Type 'number' is not assignable to type 'IQuery'
const badQuery2: IQuery = ["AND", "Hello", 123]; // error
// Type 'number' is not assignable to type 'IQuery'
const badQuery3: IQuery = 
  ["OR", query1, query2, "then", query3, query4, "if", []]; // error
// Type '[]' is not assignable to type 'IQuery'.

Playground link to code

查看 Recursive Types 的描述,不明确支持可变元组。不知道算不算下面三种中的一种:

The specific change made by this PR is that type arguments are permitted to make circular references in aliased types of the following kinds:

  • Instantiations of generic class and interface types (for example Array<Foo>).
  • Array types (for example Foo[]).
  • Tuple types (for example [string, Foo?]).

我通过从您的代码中删除可变参数运算符 ... 来测试这个理论:

type IOperator = "AND" | "OR"
type IQuery = [IOperator, Array<string | IQuery>] // NO ERROR

该更改没有导致错误,但它不支持您想要的语法。

所以我重新编写(并简化)了您的代码,如下所示,并且有效!我的理论是不支持将可变参数运算符与通用 class 实例化结合使用,无论是有意还是我不知道的错误。

type IOperator = "AND" | "OR"
type IQuery = string | [IOperator, ...IQuery[]]


// examples from your question
const query1: IQuery = "Hello"
const query2: IQuery = ["AND", "Hello", "World"]
const query3: IQuery = ["OR", ["AND", "Hello", "World"], ["AND", "Hola", "Mundo"]]

// deep nesting
const query4: IQuery = ["OR", ["AND", "Hello", ["AND", "Beautiful", "World"]], ["AND", "Hola", "Mundo"]]


//errors
const query5: IQuery = ["OR", ["NOR", "Hello", ["AND", "Beautiful", "World"]], ["AND", "Hola", "Mundo"]]
const query6: IQuery = ["OR", ["AND", 42, ["AND", "Beautiful", "World"]], ["AND", "Hola", "Mundo"]]