TypeScript 对于解构的区分联合体,控制流分析不起作用

wecizke3  于 10个月前  发布在  TypeScript
关注(0)|答案(4)|浏览(95)

Bug报告

🔎 搜索词

控制流分析,解构赋值

🕗 版本与回归信息

这个已经存在了,但我认为在4.6中引入这个功能时可能已经修复了。

⏯ Playground链接

带有相关代码的Playground链接

💻 代码

  1. export const reduce = <S, T extends S>(self: Iterable<T>, operation: (acc: S, t: T) => S): S => {
  2. const iter = self[Symbol.iterator]();
  3. let { value, done }: IteratorResult<T, unknown> = iter.next();
  4. if (done) throw new Error("Empty iterable can't be reduced.");
  5. let acc: S = value; // Type 'unknown' is not assignable to type 'S'.
  6. while (!done) {
  7. acc = operation(acc, value);
  8. ({ value, done } = iter.next());
  9. }
  10. return acc;
  11. };

请注意,如果你不向上转型 iter.next() ,你将不会得到错误,但这并不是出于好的原因:

  1. let {value, done} = iter.next(); // type is IteratorResult<T, any>
  2. if (done) throw new Error("Empty iterable can't be reduced.");
  3. // value is any and can be assigned to anything

一个更小的仓库是:

  1. export function foo(iter: Iterator<number, string>) {
  2. let { value, done } = iter.next();
  3. if (done) return;
  4. let acc: number = value; // Type 'string | number' is not assignable to type 'number'. Type 'string' is not assignable to type 'number'.
  5. return acc;
  6. }

🙁 实际行为

上面最后一个例子中的 value 被推断为 number | string ,它应该是 number

🙂 预期行为

我希望有与没有解构相同的推断:

  1. export const reduce2 = <S, T extends S>(self: Iterable<T>, operation: (acc: S, t: T) => S): S => {
  2. const iter = self[Symbol.iterator]();
  3. let result: IteratorResult<T, unknown> = iter.next();
  4. if (result.done) throw new Error("Empty iterable can't be reduced.");
  5. let acc: S = result.value; // result.value is T (extends S)
  6. while (!result.done) {
  7. acc = operation(acc, result.value);
  8. result = iter.next();
  9. }
  10. return acc;
  11. };
wdebmtf2

wdebmtf21#

  1. 你忘记填写用于提交bug报告的问题模板。
  2. 对于解构类型的CFA需要推断类型,但你明确地设置了类型。从你的变量声明中删除 : IteratorResult<T, unknown> 部分,它就可以正常工作了。
iswrvxsc

iswrvxsc2#

好的,对不起,我会填写它。但是,留下IteratorResult<T, unknown>的bug并没有被修复,请参阅我的最后一条评论,它只是不会报错,因为它被推断为任意类型,而不是未知类型(默认情况下第二个泛型参数是任意类型)。
你也可以尝试:

  1. function foo(iter: Iterator<number, unknown>) {
  2. let { value, done } = iter.next();
  3. if (done) throw new Error("Empty iterable can't be reduced.");
  4. let acc = value; // unknown, not number
  5. }
hmtdttj4

hmtdttj43#

我还有一个例子:

  1. export const last = <T>(self: Iterable<T>): T => {
  2. let { found, last }: { found: false; last?: undefined } | { found: true; last: T } = {
  3. found: false,
  4. };
  5. for (const it of self) {
  6. ({ found, last } = { found: false, last: it });
  7. }
  8. if (!found) throw new NoSuchElementException("Iterable contains no element matching the predicate.");
  9. // can not infer last to be T
  10. return last;
  11. };

@RyanCavanaugh@MartinJohns

q5iwbnjs

q5iwbnjs4#

A较短的constlet。示例取自#46266

  1. type Action =
  2. | { kind: 'A', payload: number }
  3. | { kind: 'B', payload: string };
  4. function f11(action: Action) {
  5. const { kind, payload } = action; // works
  6. if (kind === 'A') {
  7. payload.toFixed();
  8. }
  9. if (kind === 'B') {
  10. payload.toUpperCase();
  11. }
  12. }
  13. function _f11(action: Action) {
  14. let { kind, payload } = action; // doesn't work
  15. if (kind === 'A') {
  16. payload.toFixed();
  17. }
  18. if (kind === 'B') {
  19. payload.toUpperCase();
  20. }
  21. }
展开查看全部

相关问题