TypeScript泛型参数推断异常:never类型错误排查
TS2345: Argument of type 'number' is not assignable to parameter of type 'never'.
type Equal<T1, T2> = T1 extends T2 ? (T2 extends T1 ? true : false) : false; type AnyFunction = (...a: any) => any; type Defined<Type> = Type extends undefined ? false : true; type NotNull<Type> = Type extends undefined ? false : Type extends null ? false : true; type ALotOf<T> = Record<any, T>; type Constructor = { new (...args: any): any }; type ConstructorArray = Array<Constructor>; type ConstructorLikeSimpleTypedProp = | ConstructorArray | Constructor | null | undefined; type AnyOtherSimpleTypedProp = any; type SimpleTypedProp = ConstructorLikeSimpleTypedProp | AnyOtherSimpleTypedProp; type PropDefault = any; type ComplexTypedProp = { type?: SimpleTypedProp; default?: PropDefault; required?: boolean | null; }; type TypedProp = SimpleTypedProp | ComplexTypedProp; type UntypedProp = string; type TypedPropList = ALotOf<TypedProp>; type UntypedPropList = Array<UntypedProp>; type PropList = TypedPropList | UntypedPropList; type ConstructorLikeSimpleTypedPropType< T extends ConstructorLikeSimpleTypedProp > = T extends ConstructorArray ? InstanceType<T[number]> : T extends Constructor ? InstanceType<T> : any; type AnyOtherSimpleTypedPropType<T extends AnyOtherSimpleTypedProp> = T; type ValidAnyOtherDefault< Type extends SimpleTypedProp, Default extends AnyFunction > = Type extends AnyOtherSimpleTypedProp ? Equal<ReturnType<Default>, AnyOtherSimpleTypedPropType<Type>> extends true ? Equal<Parameters<Default>, []> : false : false; type ValidAnyFunctionDefault< Type extends SimpleTypedProp, Default extends AnyFunction > = Type extends AnyFunction ? Equal<ReturnType<Default>, ReturnType<Type>> extends true ? Equal<Parameters<Default>, Parameters<Type>> : false : ValidAnyOtherDefault<Type, Default>; type ValidConstructorLikeDefault< Type extends SimpleTypedProp, Default extends AnyFunction > = Type extends ConstructorLikeSimpleTypedProp ? Equal< ReturnType<Default>, ConstructorLikeSimpleTypedPropType<Type> > extends true ? Equal<Parameters<Default>, []> : false : ValidAnyFunctionDefault<Type, Default>; type ValidFunctionConstructorDefault< Type extends SimpleTypedProp, Default extends AnyFunction > = Type extends FunctionConstructor ? true : ValidConstructorLikeDefault<Type, Default>; type ValidTypeAndDefault< Type extends SimpleTypedProp | undefined, Default extends PropDefault | undefined > = Defined<Default> extends true ? Default extends AnyFunction ? NotNull<Type> extends true ? ValidFunctionConstructorDefault<Type, Default> : Equal<Parameters<Default>, []> : false : true; type ValidComplexTypedProp<T extends ComplexTypedProp> = ValidTypeAndDefault< T["type"], T["default"] >; type ValidTypedProp<T extends TypedProp> = T extends ComplexTypedProp ? ValidComplexTypedProp<T> : T extends SimpleTypedProp ? true : false; type MarkEachTypedProp<P extends TypedPropList> = { [p in keyof P]: ValidTypedProp<P[p]>; }; type ValidPropList<P extends PropList> = P extends UntypedPropList ? true : P extends TypedPropList ? false extends MarkEachTypedProp<P>[keyof MarkEachTypedProp<P>] ? false : true : false; type TrueAnyFalseNever<T extends boolean> = T extends true ? any : never; type NeverOrAny<T> = T extends never ? never : any; function prop<P extends PropList>( ok: NeverOrAny<TrueAnyFalseNever<ValidPropList<P>>>, o: P ): P { return o; } export default prop(1, { A: { type: Boolean, required: true, default(): boolean { return true; }, }, B: { type: String, default(): string { return "hello"; }, }, C: { type: String, default(): string { return "world"; }, }, D: { type: String, }, E: { type: String, required: true, }, });
我期望prop()函数的第一个参数类型被推断为any,但实际却得到never类型,导致出现上述TS2345错误。
我想要实现TypeScript中的“自约束”功能:让TypeScript根据函数参数推断泛型类型,同时要求该泛型满足基于自身的约束规则——具体来说:
- 当
ComplexTypedProp同时存在type(Constructor类型)和default函数时,default函数的返回值必须匹配type对应的InstanceType; - 不同字段的
type可以是不同的Constructor类型。
由于无法直接用简单类型(如Record<any, Type<T> | TypeAndDefault<T>>)表达该规则(T会被强制统一为同一类型),我构建了一个检查器类型来验证推断出的泛型是否符合规则:若符合则检查器类型为any,否则为never。
prop()函数的第一个参数使用该检查器类型,第二个参数为泛型P。若P符合规则,第一个参数可传入任意值;若不符合则参数类型变为never,触发编译错误以提示用户。
经测试,MarkEachTypedProp<P>能正确将o参数的所有顶级字段映射为true类型,但ValidPropList中的条件判断false extends MarkEachTypedProp<P>[keyof MarkEachTypedProp<P>] ? false : true被推断为false,进而导致第一个参数类型变为never。将该条件中的false改为true时,错误消失。请求排查该条件判断异常的原因。
问题出在ValidPropList的条件判断逻辑上:false extends MarkEachTypedProp<P>[keyof MarkEachTypedProp<P>] 的语义是检查false是否是所有属性类型的子类型,但你实际需要的是检查是否存在任何一个属性类型是false。
在TypeScript中,T[keyof T]会生成所有属性类型的联合类型。当使用X extends Y且Y是联合类型时,只有X是联合类型中每一个成员的子类型时,结果才为true。所以当MarkEachTypedProp<P>的所有属性都是true时,false extends true的结果是false,这就导致ValidPropList返回false,进而触发TrueAnyFalseNever返回never。
正确的逻辑应该是检查联合类型中是否全为true,可以通过MarkEachTypedProp<P>[keyof MarkEachTypedProp<P>] extends true ? true : false实现——如果所有属性类型都能赋值给true(即没有false),则返回true,否则返回false。
修改后的ValidPropList类型如下:
type ValidPropList<P extends PropList> = P extends UntypedPropList ? true : P extends TypedPropList ? MarkEachTypedProp<P>[keyof MarkEachTypedProp<P>] extends true ? true : false : false;
修改后,当MarkEachTypedProp<P>的所有属性都是true时,ValidPropList返回true,第一个参数类型变为any,即可正常传入数值1。
内容的提问来源于stack exchange,提问作者Tan Telephone

