两个泛型函数都能接收数字,TypeScript 却报 This expression is not callable
联合类型包含两个泛型调用签名时,参数对两边都合法仍可能无法调用。按调用方真正需要的公共签名收窄接口,避免用 any 隐藏契约。
一个泛型函数接受任意值,另一个只接受数字,把它们写成联合类型后调用 foo(1),TypeScript 仍可能报 This expression is not callable.。这里不一定是参数 1 不合法,而是检查器无法合并两条泛型签名。
具体结构在 TypeScript 6.0.3、7.0.2 中都有表现:
declare const foo:
| (<T>(value: T) => T)
| (<T extends number>(value: T) => T);
const result = foo(1);
错误后面通常还跟着 has signatures, but none of those signatures are compatible with each other。上例只声明函数类型,没有提供实现,不能直接拿去当运行程序。
共同支持一个参数,不等于能自动合并全部泛型
函数联合表示运行时拿到其中一种函数。调用必须同时适合可能出现的每个成员。TypeScript 支持合并一部分联合调用签名,但这项机制有限制:联合中至多一个成员有泛型签名,至多一个成员有多重重载。
上例两边都是泛型,超出这条合并路径。不能把它推广成“函数联合都不能调用”,也不应因为数字符合两个约束就断言检查器一定会逐项尝试所有实例化。
调用边界只需要数字时,暴露公共数字签名
如果业务入口确实只接收数字,且两种函数都遵守原来的输入输出契约,可以在这个边界表达较小的共同能力:
declare const foo:
| (<T>(value: T) => T)
| (<T extends number>(value: T) => T);
const callNumber: (value: number) => number = foo;
const result = callNumber(1);
这个赋值由检查器检查,没有把函数强制断言成新类型。代价是 result 的静态类型变成 number,不再保留输入数字字面量的精确关联。若调用方依赖该关联,就不能照搬这个较宽接口;应在生成函数的位置统一真实的泛型签名,或在调用前通过有业务意义的判别信息进入明确分支。
不要把联合改成交叉类型来消除错误。交叉函数类型会宣称同一个值同时提供两边能力,而联合值未必有这个保证。as any 也会让不被两边共同接受的参数混进调用。
检查修订后的接口有没有说过头
保留原联合定义,确认赋值到公共接口仍通过项目的实际类型检查,并对不应接受的字符串保留失败用例。检查结果类型是否满足下游,不能只看红线消失。
若函数来自第三方声明,优先在自己控制的适配边界写公共能力,保留库原类型。这里属于现有签名归约的能力边界,不构成关闭严格类型检查的理由。


