如何让编译器抱怨无效类型的定义,而不是在使用类型时抱怨

How to make compiler complain at definition on an invalid type instead complaining while type is used

目的是定义一个只接受一定数量的强制参数的函数类型。

感谢@jcalz 定义类型 OnlyTupleRequired 允许删除任何可选参数,因此即使它们包含可选参数,我也可以知道参数的长度,我'我已经成功了。

现在我希望编译器抱怨函数的定义而不是使用它们 - 任何声明 'should error' 的注释都应该发出错误而不会。

这是 link 到 playground

import {L, A, N} from 'ts-toolbelt'

type OnlyTupleRequired <T extends L.List, U extends L.List = []> = {
  0: T extends [infer F, ...(infer R)] ? OnlyTupleRequired <R, [...U, F]> : U
  1: U
} [A.Extends <Partial <T>, T>]

type MaxParametersCount = 2

type FunctionT <P extends L.List = any, R = any> = {
  1: (...params: P) => R
  0: never
} [N.LowerEq <L.Length <OnlyTupleRequired <P>>, MaxParametersCount>]

declare const f0: FunctionT                     // should be () => any
declare const f0o: FunctionT<[1?]>              // should be (a: 1?) => any
declare const f0r: FunctionT<1[]>               // should be (...a: 1 []) => any
declare const f1: FunctionT<[1]>                // should be (a: 1) => any
declare const f1o: FunctionT<[1, 1?]>           // should be (a: 1, b?: 1) => any
declare const f1r: FunctionT<[1, ...1[]]>       // should be (a: 1, ...b: 1 []) => any
declare const f2: FunctionT<[1, 1]>             // should be (a: 1, b: 1) => any
declare const f2o: FunctionT<[1, 1, 1?]>        // should be (a: 1, b: 1, c?: 1) => any
declare const f2r: FunctionT<[1, 1, ...1[]]>    // should be (a: 1, b: 1, c?: 1) => any
declare const f3: FunctionT<[1, 1, 1]>          // did not error while expecting one
declare const f3o: FunctionT<[1, 1, 1, 1?]>     // did not error while expecting one
declare const f3r: FunctionT<[1, 1, 1, ...1[]]> // did not error while expecting one

f0 ()                                           // works as expected
f0o ()                                          // works as expected
f0r ()                                          // works as expected
f1 (1)                                          // works as expected
f1o (1, 1)                                      // works as expected
f1r (1, 1, 1)                                   // works as expected
f2 (1, 1)                                       // works as expected
f2o (1, 1, 1)                                   // works as expected
f2r (1, 1, 1, 1)                                // works as expected
f3 (1, 1, 1)                                    // error as expected 'never has no call sig...'
f3o (1, 1, 1)                                   // error as expected 'never has no call sig...'
f3r (1, 1, 1)                                   // error as expected 'never has no call sig...'

所以您对 FunctionT<P, R> 的定义不会阻止将“坏”P 分配给它。您拥有的唯一 constraintL.List(ts-toolbelt 特有的东西,我猜,只是 ReadonlyArray<any> ‍♂️)。但是您特别关心确保它最多具有两个必需参数(或类似参数)。所以你想确保 P extends L.ListN.LowerEq<OnlyTupleRequired<P>["length"], MaxParametersCount> extends 1。当前,如果后一项检查不正确,您将 FunctionT<P, R> 评估为 never。但也许我们可以重写定义,使 P 实际上受到约束:

type FunctionT<P extends (
    N.LowerEq<OnlyTupleRequired<P>["length"], MaxParametersCount> extends 1
    ? L.List : never
) = any, R = any> = (...params: P) => R

恰好被录取了;有时编译器会对循环约束感到不满(其中 P 直接出现在约束中),在这种情况下,您有时可以(ab)使用 generic parameter defaults 来规避此类问题:

type FunctionT<
    P extends L.List & C = any,
    R = any,
    C = N.LowerEq<OnlyTupleRequired<P>["length"], MaxParametersCount> extends 1
      ? unknown : never
    > = (...params: P) => R

但在这种情况下没有必要。


让我们确保这符合您的要求:

declare const f0: FunctionT; // okay
declare const f0o: FunctionT<[1?]>; // okay
declare const f0r: FunctionT<1[]>; // okay
declare const f1: FunctionT<[1]>; // okay
declare const f1o: FunctionT<[1, 1?]>; // okay
declare const f1r: FunctionT<[1, ...1[]]>; // okay
declare const f2: FunctionT<[1, 1]>; // okay
declare const f2o: FunctionT<[1, 1, 1?]>; // okay
declare const f2r: FunctionT<[1, 1, ...1[]]>; // okay
declare const f3: FunctionT<[1, 1, 1]>; // error
declare const f3o: FunctionT<[1, 1, 1, 1?]>; // error
declare const f3r: FunctionT<[1, 1, 1, ...1[]]>; // error

看起来不错。工作版本继续按预期运行。三个错误的版本会在定义站点处为您提供编译器错误。请注意,类型本身不再评估为 never,因此如果您继续使用 f3f3of3r,您不一定会收到其他错误:

f3(1, 1, 1); // no error

如果必须,您可以同时使用这两种技巧,以便输出类型也为 never。但我个人认为 f3 定义处的编译器错误意味着你之后对 f3 所做的任何事情都是可疑的,如果它碰巧没有给出额外的错误,那就没什么大不了的。不过,由你决定。

Playground link to code