Bug报告
Bug报告
🔎 搜索词
Assert函数、可赋值性、不一致的类型保护
🕗 版本与回归信息
- 这种行为在每个我尝试过的版本中都存在,我也查阅了关于类型系统行为的常见问题解答条目
⏯ Playground链接
包含相关代码的Playground链接
💻 代码
const assertNumber = (x: unknown): asserts x is number => {
if (typeof x !== 'number') {
throw Error("not a number")
}
}
// suspicious assignment...
const assertString: (x: unknown) => asserts x is string = assertNumber
const x: unknown = 100;
assertString(x); // Because `assertString` is actually `assertNumber`, no error is thrown
/*** x's type is now string ***/
x.substring(1); // no compile error, but runtime error!
const foo = (x: string | undefined): number => 100
// suspicious assignment...
const assertTruthy: (x: string | undefined) => asserts x = foo
const y: string | undefined = false ? 'a' : undefined;
assertTruthy(y);
y.substring(1); // no compile error, but runtime error!
🙁 实际行为
(可疑的)两个赋值操作都成功执行,没有编译错误。这种行为与类型保护的行为不一致。
🙂 预期行为
这两个赋值操作都应该被编译器拒绝。
我认为Assert函数的可赋值性规则应该与类型保护的规则一致。至少,一个没有Assert函数签名的函数被赋值给一个Assert函数类型的变量应该被拒绝。此外,对于具有"结果类型"( (x: ...) => asserts x is T
)的Assert函数签名,它应该与"结果类型"保持协变性。
4条答案
按热度按时间jbose2ul1#
至少,将一个没有Assert函数签名的函数分配给一个Assert函数类型的变量应该被拒绝。
原始笔记在#32695中没有提到这一点,但据我所知,这种逻辑的动机是我们希望与Assert函数类型绑定的绑定可以通过“普通”函数进行初始化,即:
在这里,函数表达式的返回类型仅仅是
void
,因此根据这个规则,初始化是非法的,除非我们添加了新的逻辑来例如上下文地修改函数表达式的返回类型。将具有两种不同Assert类型的东西分配给是非常草率的。我们可能会标记这个问题,但我认为过去三年的生活经验是,任何类型的Assert函数的别名非常罕见,因为它们没有任何有意义的替代或多态性。
k5hmc34c2#
别名Assert函数...没有任何有意义的替代或多态性。
我不同意,并认为这是一个合法的正确性问题!考虑以下内容,其中包括定义泛型数组Assert函数的一些其他内容:
链接到Playground
请注意,我明确地将
assertStringArray
标记为AssertFn<string[]>
,但实际上通过array(num)
提供了一个AssertFn<number[]>
,编译器对此没有问题。如果我删除了显式类型注解,它会正确地推断出类型为AssertFn<number>
。iqih9akk3#
一个可能的原因是,它要求指定Assert类型以确保它们影响CFA。
但是如果我们放入错误的类型...
我本以为
const assertIsString: TypeAssertion<number> = makeAssertion(isString);
会产生类型错误。还有一个例子:
playground
guykilcj4#
这种行为对我来说非常令人惊讶。我一直在做一个项目,该项目使用Assert函数验证传递给API端点的JSON数据。我震惊地意识到代码实际上没有类型安全。项目中的其他人也没有意识到这一点,包括编写原始代码的人。有关问题的非常简化的代码版本如下:
$x_1a^0b^1^x$