网站搜索

typescript - 如何创建递归元组来描述逻辑运算符?


是否可以在不手动描述所有不同参数数量情况的情况下描述下一种类型?

type Operator = '&&'|'||'
type Logical<T> = (
  | [Expression<T>, Operator, Expression<T> ] 
  | [Expression<T>, Operator, Expression<T>, Operator, Expression<T> ]
  | [Expression<T>, Operator, Expression<T>, Operator, Expression<T>, Operator, Expression<T> ]
  | ...
)

有效类型值:

find([['id', '=', 10], '&&', ['type', '=', 'animal']])
find([['id', '=', 10], '&&', ['type', '=', 'human'], '&&', ['age', '>=', '40']])

案例中的类型错误:

find([['id', '=', 10], ['type', '=', 'animal']])
find([['id', '=', 10], '&&', ['type', '=', 'human'], ['age', '>=', '40']])
find([['id', '=', 10], ['type', '=', 'human'], ['age', '>=', '40']])

答案#1

这可能有效:

// The type of an array's elements
type ArrayType<T extends readonly unknown[]> = T extends readonly (infer U)[] ? U : never
// An array with at least 3 elements
type AtLeast3<T> = readonly [T, T, T, ...T[]]
// Enforces that T's last element is U
type EnforceLastEl<T, U> = T extends readonly [...unknown[], U] ? T : never

// Checks if T starts with A, and then recurses
type AltArr1<T extends readonly unknown[], A, B, InitialValue> =
  T extends [infer Head, ...infer Tail]
    ? Head extends A
      // Check if the rest starts with B
      ? AltArr2<Tail, A, B, InitialValue>
      // Invalid type
      : never
    // Empty array (we are done)
    : InitialValue

// Same as Alt1 except it checks if T starts with B
type AltArr2<T extends readonly unknown[], A, B, InitialValue> =
T extends [infer Head, ...infer Tail]
    ? Head extends B
      ? AltArr1<Tail, A, B, InitialValue>
      : never
    : InitialValue

// Enforces an array alternates between A and B and also ends with the first value (A)
type AltArr<T extends AtLeast3<unknown>, A, B> = EnforceLastEl<AltArr1<T, A, B, T>, A>

// Actual stuff specific to Logical

// Because you didn't provide the definition of Expression, I'm not sure if it
// will work with the actual definition
type Expression<T> = T

type LogicalInput<T> = Expression<T> | Operator
type LogicalInputType<T extends LogicalInput<unknown>> =
  T extends LogicalInput<infer U> ? U : never

type Logical<T extends AtLeast3<LogicalInput<unknown>>> =
  AltArr<T, Expression<LogicalInputType<ArrayType<T>>>, Operator>

declare const find: <T extends AtLeast3<LogicalInput<unknown>>>(
  ...args: Logical<T> extends never
    // If T isn't valid, Logical<T> will be never, so don't accept anything
    ? [never]
    // T is valid, accept the argument
    : [expr: T]
) => void

// Errors
find([1])
find([1, '&&'])
find([1, '&&', 2, '||'])
find(['&&', 1, '||'])

// This doesn't work, unfortunately
find([1, '&&', 2])

// Works
find([1, '&&', 2] as const)
find([1, '&&', 2, '||', 3] as const)

操场链接