将类型谓词与断言函数结合使用
我正在使用ajv将一个对象对照JSON模式进行校验。简化后的代码如下:
interface ValidateFunction<T = unknown> {
(data: unknown): data is T;
}
declare const test: ValidateFunction<string>;
declare function assertIsTrue(b: boolean): asserts b is true;
function f(s: unknown): string {
const isValid = test(s);
assertIsTrue(isValid);
return s;
}
我在 return 上遇到了一个错误:
类型 'unknown' 不能赋值给类型 'string'。
我本来以为这段代码可以工作。如果把 assertIsTrue 改成 if (!valid) throw new Error();,编译器就不会报错。这是个bug吗?
解决方案
这是 microsoft/TypeScript#60904 的主题,被归类为“不是缺陷”的问题,意味着它不被视为一个bug,但至多是一个功能缺失。
你正在尝试将以下两者结合起来:
- [别名条件的控制流分析] 的实现见 [microsoft/TypeScript#44730],以及
- [断言函数] 的实现见 [microsoft/TypeScript#32695]。
但这对像 asserts b is true 这样的断言谓词并不起作用。这样的断言谓词只作用于值 b 本身,并且其行为与若直接对 b 进行控制流分析时的结果不完全相同。请参阅实现者的注释 [https://github.com/microsoft/TypeScript/pull/32695#issuecomment-517937626]。
因此,当你写出 assertIsTrue(isValid) 时,它会将 isValid 收窄为 true,但不会触发对条件别名 isValid 的收窄。
作为变通办法,不使用 asserts b is true,你可以使用 asserts b,在直接对 b 进行真值性检查时,它的行为确实与控制流分析完全一致。并非总能用这种方式重构谓词,但由于布尔值 b 只有在等于 true 时才为真,因此足矣:
declare function assertIsTrue(b: boolean): asserts b;
function f(s: unknown): string {
const isValid = test(s);
assertIsTrue(isValid);
return s; // okay
}
也有可能提交一个功能请求,允许 asserts x is true 或 asserts x is false 在 x 为别名条件的情况下按你期望的方式工作,但扩展对控制流分析的行为可能成本高昂。若某段原本不会触发更多分析的代码现在却会触发分析,这种分析可能会进一步触发分析,等等。因此,即便增加额外分析可能有用,也并非总是可行或值得。这并不意味着不应提交此类请求,只是除非在复杂性和性能方面显然值得实现,否则我不指望它会被实现。