如何让编译器抱怨无效类型的定义,而不是在使用类型时抱怨
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
分配给它。您拥有的唯一 constraint 是 L.List
(ts-toolbelt 特有的东西,我猜,只是 ReadonlyArray<any>
♂️)。但是您特别关心确保它最多具有两个必需参数(或类似参数)。所以你想确保 P extends L.List
和 N.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
,因此如果您继续使用 f3
、f3o
或 f3r
,您不一定会收到其他错误:
f3(1, 1, 1); // no error
如果必须,您可以同时使用这两种技巧,以便输出类型也为 never
。但我个人认为 f3
定义处的编译器错误意味着你之后对 f3
所做的任何事情都是可疑的,如果它碰巧没有给出额外的错误,那就没什么大不了的。不过,由你决定。
目的是定义一个只接受一定数量的强制参数的函数类型。
感谢@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
分配给它。您拥有的唯一 constraint 是 L.List
(ts-toolbelt 特有的东西,我猜,只是 ReadonlyArray<any>
♂️)。但是您特别关心确保它最多具有两个必需参数(或类似参数)。所以你想确保 P extends L.List
和 N.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
,因此如果您继续使用 f3
、f3o
或 f3r
,您不一定会收到其他错误:
f3(1, 1, 1); // no error
如果必须,您可以同时使用这两种技巧,以便输出类型也为 never
。但我个人认为 f3
定义处的编译器错误意味着你之后对 f3
所做的任何事情都是可疑的,如果它碰巧没有给出额外的错误,那就没什么大不了的。不过,由你决定。